AspectJ的切点函数非常精妙,基本上可以覆盖我们编程中可以遇到的所有连接点,因为Spring仅支持方法级别的切点,所以Spring中的切点函数是AspectJ中的一个子集。掌握切点函数即掌握了AspectJ语法的基础。
首先是在切点函数中需要使用的通配符的概念:
*:匹配任意字符
..:匹配任意字符,表示类的时候必须和*联合使用,表示入参时可以单独使用
+:表示匹配目标类以及目标类的子类
execution(),within()可以使用全部通配符。
args(),target(),this()仅能使用+通配符,不过使用和不使用的效果是一样的。
其他的切点函数不能使用通配符。
切点函数的分类:
- 方法切点函数
- 方法入参切点函数
- 目标类切点函数
- 代理类起点函数
在AspectJ中我们使用注解来定义增强类型(即增强逻辑的织入位置)
- @Before:前置增强
- @AfterReturning:后置增强
- @Around:环绕增强
- AfterThrowing:抛出异常增强
- After:不管是否抛出异常,都会执行,相当于Final增强
- DeclareParents:引介增强
在使用AspectJ配置文件的时候,需要在位置文件中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>
proxy-target-class默认是false,表示采用JDK动态代理生成目标类,如果设为true,则使用CGLib代理。
********这两者有很大区别:JDK动态代理仅能生成目标类的 接口类型,而CGLib则生成目标 类的代理类型。这对于切点函数的意义重大,这点一定需要牢记!!
切点函数详解:
1.@annotation()
表示当方法标注类设定的注解,则该方法匹配次切点
//标注了NeedTes注解的方法,在返回时将被注入以下增强 @Aspect public class TestAspect { @AfterReturning("@annotation(com.firethewhole.maventest08.aspectj.anno.NeedTest)") public void needTestFun() { System.out.println("needTestFun() executed"); } }
// 目标类 public class NaughtyWaiter implements Waiter { @NeedTest public void greetTo(String clientName) { System.out.println("NaughtyWaiter: greet to " + clientName); } }
<aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="naiveWaiter" class="com.firethewhole.maventest08.NaiveWaiter"/> <bean id="naughtWaiter" class="com.firethewhole.maventest08.NaughtyWaiter"/> <bean class="com.firethewhole.maventest08.aspectj.fun.TestAspect"/>
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/firethewhole/maventest08/aspectj/example/beans.xml"); NaiveWaiter naiveWaiter = (NaiveWaiter) ctx.getBean("naiveWaiter"); NaughtyWaiter naughtWaiter = (NaughtyWaiter) ctx.getBean("naughtWaiter"); naiveWaiter.greetTo("John"); naughtWaiter.greetTo("Tom");
输出:
NaiveWaiter.greet to John
NaughtyWaiter: greet to Tom
needTestFun() executed
2.execution()
表示执行方法匹配切点函数,这个切点函数是最常用的。
2.1 通过方法签名定义切点
execution(public * *(..)):匹配public方法。
execution(* *To(..)):匹配所有以To结尾的方法。
2.2 通过类定义切点
execution(com.firethewhole.maventest08.Waiter.*(..)):匹配Waiter接口的所有方法,所以NaughtyWaiter和NaiveWaiter中greetTo和serveTo方法都会被织入增强。
execution(com.firethewhole.maventest08.Waiter+.*(..)):不光匹配Waiter接口中定义的增强,还匹配NaiveWaiter的smile方法和NaughtyWaiter中的joke方法。
2.3 通过类包定义切点
execution(* com.firethewhole.maventest08.*(..)):匹配com.firethewhole.maventest08包下面所有类的所有方法。
execution(* com.firethewhole.maventest08..*(..)):不光包括上面的方法,还包括类这个包里面的子孙包中的类的所有方法。
execution(* com.firethewhole.maventest08..*.*Dao.find*(..)):匹配com.firethewhole.maventest08这个包,包括子孙包中以Dao结尾的类中,以find开头的方法。
2.4 通过方法入参定义切点
execution(* joke(String,int)):匹配joke方法,且第一个参数类型必须是String,第二个参数类型必须是int,有且只有这两个参数,比如NaughtyWaiter中的joke方法。
execution(* joke(String,*)):第二个参数可以是任意类型,但是必须是两个参数。
execution(* joke(String,..)):第一个参数必须是String类型,可以只有一个参数,也可以有多个参数。
execution(* joke(Object+)):匹配Object类型或者Object的子类,即所有的引用类型变量,是否包含基本类型,需要测试才知道。execution(* joke(Object))则仅匹配Object,其他入参类型不匹配。
3.args()
表示入参类型匹配,注意这里是实际入参类型动态匹配,所以也包括类子类。
execution(com.firethewhole.maventest08.Waiter):如果入参是Waiter或者NavieWaiter或者NaughtyWaiter,则匹配该切点。
4.@args()
如果入参标注类该切点,则匹配该切点。
这里涉及到类3个概念,切点函数所标识的注解类型,入参类型,运行时实际入参类型类型。
需要记住:标注注解的类型如果在继承关系上是入参类型的子类,则该类型及其子类都匹配切点,如果标注注解的类型在继承关系上是入参类型法父类,则都不匹配。
5.within()
属于execution()切点函数的子集,连接点仅能指定到类级别。
6.@within()
匹配标注类指定注解的类,包括该指定注解类的子类。
7.@target()
仅匹配标注类指定注解的类,不包括该标注注解类的子类!
8.target()
匹配切点函数所指定的类型及其子类的所有方法
9.this()
如果该代理对象按类型匹配目标类,则匹配该切点。这个函数和target是有区别的,在使用引介增强是,为NaiveWaiter加入SmartSeller的增强后,this(com.firethewhole.maventest08.Seller)将匹配NaiveWaiter类。
// 引介增强,会NaiveWaiter织入Seller增强,实现类为SmartSeller @Aspect public class EnableSellerAspect { @DeclareParents(value="com.firethewhole.maventest08.NaiveWaiter", defaultImpl=SmartSeller.class) public Seller seller; }
// 代理增强切点 @Aspect public class TestAspect { @AfterReturning("this(com.firethewhole.maventest08.Seller)") public void thisTest() { System.out.println("this Test() executed"); } }
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/firethewhole/maventest08/aspectj/example/beans.xml"); Waiter naiveWaiter = (Waiter) ctx.getBean("naiveWaiter"); naiveWaiter.greetTo("John"); naiveWaiter.serveTo("John"); ((Seller)naiveWaiter).sell("Beer", "John");
输出:
NaiveWaiter.greet to John
this Test() executed
NaiveWaiter.serve to John
this Test() executed
SmartSeller: sell Beer to John
*****这里和书上不一样,书上最后一个sell方法也被织入增强,但是实际上是没有。
切点的复合运算
使用&& || !或者and or not组合各个切点函数
@Aspect public class TestAspect { @After("within(com.firethewhole.maventest08.*) && execution(* greetTo(..))") public void greetToFun() { System.out.println("--greetToFun() executed!--"); } @Before("!target(com.firethewhole.maventest08.NaiveWaiter) && execution(* serveTo(..))") public void notServeInNaiveWaiter() { System.out.println("--notServeInWaiveWaiter() executed!--"); } @AfterReturning("target(com.firethewhole.maventest08.Waiter) || target(com.firethewhole.maventest08.Seller)") public void waiterOrSeller() { System.out.println("--waiterOrSeller() executed!--"); } }
命名切点
直接在AspectJ类中使用的切点函数是匿名切点,我们可以在一个专门的类中,将所有切点函数都命名然后使用。
// 注意这里的访问修饰符,private表示仅能在该类中使用,和正常的修饰符是一样的 public class TestNamePointcut { @Pointcut("within(com.firethewhole.maventest08.*)") private void inPackage() { } @Pointcut("execution(* greetTo(..))") protected void greetTo() { } @Pointcut("inPackage() and greetTo()") public void inPkgGreetTo() { } }
@Aspect public class TestAspect { @Before("com.firethewhole.maventest08.aspectj.advanced.TestNamePointcut.inPkgGreetTo()") public void pkgGreetTo() { System.out.println("--pkgGreetTo() executed!--"); } @Before("!target(com.firethewhole.maventest08.NaiveWaiter) && com.firethewhole.maventest08.aspectj.advanced.TestNamePointcut.inPkgGreetTo()") public void pkgGreetToNoNaiveWaiter() { System.out.println("--pkgGreetToNoNaiveWaiter() executed!--"); } }
增强织入顺序
不同的切面类靠实现Order接口来决定,相同的切面类中的增强是按照定义的顺序织入的。
访问连接点信息
// @Around增强需要使用ProceedingJoinPoint,其他的增强类型使用JoinPoint @Aspect public class TestAspect { @Around("execution(* greetTo(..)) && target(com.firethewhole.maventest08.NaiveWaiter)") public void joinPointAccess(ProceedingJoinPoint pjp) throws Throwable { System.out.println("-----joinPointAccess-----"); System.out.println("arg[0]:" + pjp.getArgs()[0]); System.out.println("signature:" + pjp.getTarget().getClass()); pjp.proceed(); System.out.println("-----JoinPointAccess-----"); } }
输出:
-----joinPointAccess-----
arg[0]:John
signature:class com.firethewhole.maventest08.NaiveWaiter
NaiveWaiter.greet to John
-----JoinPointAccess-----
绑定连接点方法入参
这个例子一定要用CGLib代理
// args()会根据参数中的name,num自动去增强逻辑bindJoinPointParams中查找实际的类型信息 @Aspect public class TestAspect { @Before("target(com.firethewhole.maventest08.NaiveWaiter) && args(name,num,..)") public void bindJoinPointParams(int num, String name) { System.out.println("-----bindJoinPointParams-----"); System.out.println("name:" + name); System.out.println("num:" + num); System.out.println("-----bindJoinPointParams-----"); } }
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/firethewhole/maventest08/aspectj/example/beans.xml"); NaiveWaiter naiveWaiter = (NaiveWaiter) ctx.getBean("naiveWaiter"); naiveWaiter.smile("John", 2);
输出:
-----bindJoinPointParams-----
name:John
num:2
-----bindJoinPointParams-----
NaiveWaiter.smile to John 2 times
绑定代理对象
@Aspect public class TestAspect { @Before("this(waiter)") public void bindProxyObj(Waiter waiter) { System.out.println("-----bindProxyObj-----"); System.out.println(waiter.getClass().getName()); System.out.println("-----bindProxyObj-----"); } }
输出:
-----bindProxyObj-----
com.firethewhole.maventest08.NaiveWaiter$$EnhancerBySpringCGLIB$$6f6d5c3b
-----bindProxyObj-----
NaiveWaiter.greet to John
可以看到实际生成的代理对象的类型。
绑定类注解对象
在使用@target或者@args时指定类注解类型,可以在增强逻辑中使用。
绑定返回值
// 在各种增强类型中,如果有返回值相关的信息可以指定 @Aspect public class TestAspect { @AfterReturning(value="target(com.firethewhole.maventest08.SmartSeller)",returning="retVal") public void bindReturnValue(int retVal) { System.out.println("-----bindReturnValue-----"); System.out.println("returnValue:" + retVal); System.out.println("-----bindReturnValue-----"); } }
输出:
SmartSeller: sell Beer to John
-----bindReturnValue-----
returnValue:100
-----bindReturnValue-----
绑定抛出异常
@Aspect public class TestAspect { @AfterThrowing(value="target(com.firethewhole.maventest08.SmartSeller)", throwing="iae") public void bindException(IllegalArgumentException iae) { System.out.println("-----bindException-----"); System.out.println("exception:" + iae.getMessage()); System.out.println("-----bindException-----"); } }