我们所说的SpringAOP,包括基于xml文档配置的AOP,也包括基于AspectJ注解的AOP,但是它们底层采用的都是动态代理技术,包括JDK动态代理与CGLib动态代理。
基于@AspectJ的AOP
简单示例:
(1)切面定义
packageaspect;
importorg.aspectj.lang.annotation.After;
importorg.aspectj.lang.annotation.AfterReturning;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Before;
//声明为切面
@Aspect
publicclass Aspect_1 {
//execution中表达式要与返回值 * 之间有空格
//定义前置增强
@Before("execution(* greet(..))")
public void before(){
System.out.println("Welcome to our hotel !");
}
//定义后置增强,并绑定目标对象方法的返回值
//returning 属性用于绑定目标方法的返回值,并传递给增强的入参
@AfterReturning(value="execution(*service(..))",returning="award")
public void thanks(String award){
System.out.println("thanks for your"+award+"'s");
}
//定义final增强
@After("execution(* greet(..))")
public void after(){
System.out.println("Good Bye !");
}
}
(2)目标类定义
packageaspect;
publicclass Waiter {
public void greet(String name){
System.out.println("What can I do for you ?"+"Mr"+name);
}
public String service(){
return "very good";
}
}
(3)xml文档中的配置
<!--利用aspectj实现增强 -->
<aop:aspectj-autoproxyproxy-target-class="true"/><!--实现自动代理,为匹配切面中切点表达式的bean自动创建代理 -->
<bean id="waiter" class="aspect.Waiter"/>//目标类
<bean class="aspect.Aspect_1"/>//切面
@Aspect注解将POJO 声明为切面,第三方处理处理程序便根据它判断类是不是切面(需要额外的包)
@Before()注解表示为前置增强,括号的内容为目标切点表达式
在XML文件中,<aop:aspectj-autoproxy/>自动为Spring容器中那些匹配@AspectJ切面的Bean创建代理类,并完成切面织入。(但是在Spring内部仍然采用自动代理创建类进行创建)
<aop:aspectj-autoproxy/>有个属性proxy-targer-class,默认为false,表示使用JDK动态代理实现,如果声明为true,则使用CGLib动态代理实现,不过,即使proxy-targer-class设置为false,如果目标类没有声明接口,Spring自动使用CGLib代理
@AspectJ语法基础
1.切点表达式函数
切点表达式由关键字与操作参数组成,如:execution(*greet(..)),execution代表关键字,表示目标类执行某一方法,* greet(..)代表操作参数,用于匹配目标方法,两者联合起来代表目标类方法的连接点。
函数 | 入参 | 说明 |
execution() | 方法匹配模式串 | 表示满足某一匹配模式的所有目标类方法连接点 |
args() | 类名 | 通过目标方法入参对象类型指定连接点 |
within() | 类名匹配串 | 特定域下的所有连接点 |
target() | 类名 | 指定类型下的所有连接点 |
- 通配符
* 匹配任意字符,但是只能匹配一个元素
.. 匹配任意字符,可以匹配多个元素
+ 必须跟在类名后面,匹配指定类型的所有类
3. 不同的增强类型
@Before 前置增强,value属性用于指定切点
@AfterReturning后置增强,value属性用于指定切点,pointcut用于指定切点的信息,可以覆盖value的值,returning属性用于将目标方法的返回值绑定给增强的方法
@Around 环绕增强,value属性用于指定切点
@AfterThrowing抛出异常增强,value属性用于指定切点,pointcut用于指定切点的信息,可以覆盖value的值,throwing属性用于将目标方法抛出的异常绑定到增强方法中
@After Final增强,不管是抛出异常还是正常退出,该增强都会得到执行
@DeclareParents引介增强,value属性用于指定切点,表示在哪个目标类上添加引介增强,defaultImp属性表示默认的接口实现类,这样目标类就实现了指定的接口。
public interface Seller {
public void sell();
}
public class SellerImp implements Seller {
@Override
public void sell() {
System.out.println("I can sell somrthing !");
}
}
//定义引介增强
//value属性表示要增加引介增强的目标类
//defaultImp属性表示,默认的接口实现类
@DeclareParents(value="aspect.Waiter",defaultImpl=SellerImp.class)
public Seller seller;//要实现的接口
//可以进行强制类型转换
Seller seller=(Seller)waiter;
seller.sell();
@AspectJ语法进阶
1.切点复合运算
&& 与操作符,相当于切点的交集(and)
|| 或操作符,相当于切点的并集(or)
! 非操作符,相当于切点的反集(not)
- 切点命名
在上面例子中,切点直接声明在增强方法处,成为匿名切点,只能在声明处使用。
可以通过@Pointcut注解对切点进行命名
命名切点使用方法名作为切点的名称。
命名切点:
public class PointCut {
//@Pointcut中为切点表达式,代替原来在@Before()中的切点表达式
//并将切点命名为before
@Pointcut("execution(* greet(..))")
public void before(){}
//@Pointcut中为切点表达式,代替原来在@AfterReturning()中的切点表达式
//并将切点命名为afterReturning
@Pointcut("execution(* service(..))")
public void afterReturning(){}
//@Pointcut中为切点表达式,代替原来在@After()中的切点表达式
//并将切点命名为after
@Pointcut("execution(* greet(..))")
public void after(){}
}
根据切点名称引用切点:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class Aspect_1 {
//应用命名的切点名称,代替原来的切点表达式
//定义前置增强
@Before("PointCut.before()")
public void before(){
System.out.println("Welcome to our hotel !");
}
//定义后置增强,并绑定目标对象方法的返回值
//returning 属性用于绑定目标方法的返回值,并传递给增强的入参
@AfterReturning(value="PointCut.afterReturning()",returning="award")
public void thanks(String award){
System.out.println("thanks for your"+award+"'s");
}
//定义final增强
@After("PointCut.after()")
public void after(){
System.out.println("Good Bye ! Welcome again !");
}
}
- 访问连接点信息
将增强方法的第一个参数声明为JoinPoint/ProceedingJoinPoint表示连接点对象,用于访问连接点上下文信息。
JoinPoint:
getArgs()//用于获取连接点参数
getThis()//获取代理对象本身
getTarget()//获取目标对象
ProceedingJoinPoint:
继承JoinPoint,用于环绕增强
proceed()//通过反射执行目标连接点处的方法
//定义环绕增强
//使用ProceedingJoinPoint访问连接点信息
@Around("execution(* show(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint){
//获取目标方法第一个参数
System.out.println("Hello !Mr"+proceedingJoinPoint.getArgs()[0]);
//执行目标方法
try {
proceedingJoinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("Please wait a moment !");
}
- 绑定连接点方法参数
args()中还可以指定参数名,将目标对象连接点方法的参数绑定到增强方法中。
参数绑定过程为:args()先根据其中的参数名称在增强方法中查找到名称相同的入参并获取对应的类型,如args(nameString,idint)与public voidbefore(String nameString,int idint),args()中参数名称必须与增强方法中参数名称相同,这样就知道匹配的连接点方法参数类型。其次,连接点方法入参类型所在位置由参数名在args()中声明的位置决定。
增强方法
//用于绑定连接点参数
@Before("execution(*print(..))&&args(nameString,idint)")
public void before(String nameString,int idint){
System.out.println("开始绑定!");
System.out.println(nameString+" "+idint);
}
切点方法:
//连接点方法对应类型参数的位置由args()中决定
public void print(String name,int id){
System.out.println(name+" "+id);
}