概述
@AspectJ使用Java5.0注解和正规的AspectJ的切点表达式语言描述切面, 由于Spring只支持方法的连接点,所以Spring仅支持部分AspectJ的切点语言。
下文阐述的AspectJ切点表达式语言,以AspectJ5.0版本为准。
切点表达式函数
AspectJ5.0的切点表达式由关键字和操作参数组成.
比如 execution(* greetTo(..))
, execution
为关键字, * greetTo(..)
为操作参数。
在这里execution代表目标类执行某一方法,而 * greetTo(…)描述目标方法的匹配模式串,二者联合起来标识目标类greetTo()方法的连接点。 为了方便描述,将execution()
称为函数,而将匹配串 * greetTo(..)
称作函数的入参。
Spring支持9个@AspectJ切点表达式函数,它们用不同的方式描述目标类的连接点
根据描述对象的不同,大致可以分为4类
- 方法切点函数:通过描述目标类方法的信息定义连接点
- 方法入参切点函数:通过描述目标类方法入参的信息定义连接点
- 目标类切点函数:通过描述目标类类型的信息定义连接点
- 代理类切点函数:通过描述目标类的代理类的信息定义连接点
下面我们来看下具体的函数
方法切点函数
execution()
入参:方法匹配模式串
说明:表示满足某一匹配模式的所有目标类方法连接点,如 execution(* greetTo(…))表示所有目标类中的greetTo()方法,greetTo()方法可以带带任意的入参和任意的返回值
@annotation()
入参:方法注解类名
说明:表示标注了某特定注解的目标类方法连接点, 比如@annotation(com.xgj.annotation.NeedTest)表示任何标注了@NeedTest注解的目标类方法。
方法入参切点函数
args()
入参:类名
说明:通过判断目标类方法运行时入参对象的类型定义指定连接点,如args(com.xgj.Waiter)表示所有有且仅有一个按类型匹配于Waiter入参的方法。
@args()
入参:类型注解类型
说明:通过判断目标类方法运行时入参对象的类是否标注了特定的注解指定连接点。 比如@args(com.xgj.Monitor)表示任何这样的一个目标方法,它有一个入参且入参对象的类标注@Monitor注解
目标类切点函数
within()
入参:类名匹配串
说明:表示特定域下的所有连接点,比如 within(com.xgj.service.*)
表示com.xgj.service包中所有的连接点,即包中所有类的所有方法; 而 within(com.xgj.service.*Service)
表示在com.xgj.service包中所有以Service结尾的类的所有连接点
target()
入参:类名
说明:假如目标类按类型匹配与指定类,则目标类的所有连接点匹配这个切点。 比如通过target(com.xgj.Waiter)定义的切点,Waiter及Waiter实现类NaiveWaiter中的所有连接点都匹配该切点
@within()
入参:类型注解类名
说明:假如目标类按类型匹配于某个类A,且类A标注了特定的注解,这目标类的所有连接点匹配这个切点。 比如@within(com.xgj.Monitor)定义的切点,加入Waiter实现了@Monitor注解,这Waiter及Waiter的实现类NaiveWaiter的说哟连接点都匹配这个切点
@target()
入参:类型注解类名
说明:假如目标类标注了特定注解,则目标类的所有连接点都匹配该切点。 如@target(com.xgj.Monitor),假设NaiveWaiter标注了@Monitor注解,则NaiveWaiter的所有连接点都匹配这个切点
代理类切点函数
this
入参:类名
说明:代理类按类型匹配于指定类,则被代理的目标类的所有连接点都匹配该切点。
注意事项
除了上述的函数外,@AspectJ还有其它的函数 ,比如call()、initialization()等,但是不能够在Spring中使用,否则会抛出IllegalArgumentException异常。
在函数入参中使用通配符
有些函数的入参可以接受通配符,@AspectJ支持三种通配符
*
*
表示匹配任意字符,但它只能匹配上下文中的一个元素
..
..
表示匹配任意字符,可以匹配上下文中的 多个元素, 但在标识类时,必须和 *
联合使用,而在表示入参时则单独使用
+
+
表示按照类型匹配指定类的所有类,必须跟在类名后面,比如com.xgj.Service+。 继承或扩展指定类的所有类,同时还包含指定类本身。
##支持通配符的函数说明
- 支持所有通配符 execution()和within() , 比如
within(com.xgj.*)
within(com.xgj.service..*.*Service+)
- 仅支持“+”通配符: args()、this()和target(). 比如args(com.xgj.Waiter+) 、target(java.util.List+)等。 虽然支持+通配符,但是意义不大。 对于这些函数来讲使用+和不使用+是等价的
- 不支持通配符: @args()、@within、@target 和 @annotation.
此外,args() this() target() @args() @within() @target() @annotation() 这7个函数除了可以指定类名外,也可以指定变量名,并将目标对象中的变量绑定到增强的方法中。
逻辑运算符
切点表达式由切点函数组成,切点函数之间可以进行逻辑运算,组成复合切点。
Spring支持以下切点运算符
&&
与操作符,相当于切点的交集运算。
如果在Spring的XML配置文件中使用切点表达式,由于&是XML特殊字符,所以使用转义字符串&&
表示。
为了方便,Spring提供了一个等效的运算符and, 比如within(com.xgj..*) and args(String)
表示在com.xgj包下所有类(当前包以及子孙包)拥有一个String入参的方法;
||
或操作符,相当于切点的并集运算,or是等效的操作符。如within(com.xgj..*) || args(String)
表示在com.xgj包下的所有类的方法,或者所有拥有一个String入参的方法;
!
非操作符,相当于切点的反集运算,not是等效的操作符。如!within(com.xgj.*)
表示所有不在com.xgj包下的方法。
注意:
在Spring中使用and or 和 not操作符时,允许不在前面添加空格,比如within(com.xgj..*)andnotargs(String)
和 within(com.xgj..*) and not args(String)
是等效的,不过为了程序的可读性,我们还是要求在操作符的前后添加空格。
另外,如果not位于切点表达式的开头,则必须在开头添加一个空格,否则会产生解析错误。 比如 not within(com.xgj..*)
不同增强类型
@AspectJ为各种的增强类型提供了不同的注解类,它们位于org.aspectj.lang.annotation.*包中,这些注解类拥有若干个成员,可以通过这些成员完成定义切点信息、绑定连接点参数等操作;
此外,这些注解的存留期限都是RetentionPolicy.RUNTIME,标注目标都是ElementType.METHOD。
@Before
前置增强,相当于BeforeAdvice的功能,
Before注解类拥有两个成员:
- value:该成员用于定义切点;
- argNames:由于无法通过Java反射机制获取方法入参名,所有如果在Java编译时未启动调试信息或者需要在运行期解析切点,就必须通过这个成员指定注解所标注增强方法的参数名(注意两者名字必须完全相同),多个参数名用逗号分隔。
@AfterReturning
后置增强,相当于AfterReturningAdvice,
AfterReturning注解类拥有4个成员:
- value:该成员用于定义切点;
- pointcut:表示切点的信息,如果显式指定pointcut值,它将覆盖value的设置值,可以将pointcut成员看成是value的同义词;
- returning:将目标对象方法的返回值绑定给增强的方法;
- argNames:如前所述。
@Around
环绕增强,相当于MethodInterceptor,
Around注解类拥有两个成员:
- value:该成员用于定义切点;
- argNames:如前所述。
@Aspect public class AtTargetAspect { @Around("@target(com.xgj.aop.spring.advisor.aspectJ.function.attarget.Mark)") public void crossCuttingCode(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("****AtTargetAspect.crossCuttingCode() : " + joinPoint.getSignature().getName() + ": Before Method Execution"); try { joinPoint.proceed(); } finally { // Do Something useful, If you have } System.out.println("****AtTargetAspect.crossCuttingCode() : " + joinPoint.getSignature().getName() + ": After Method Execution"); } }
@AfterThrowing
抛出增强,相当于ThrowsAdvice.
AfterThrowing注解类拥有4个成员:
- value:该成员用于定义切点;
- pointcut:表示切点的信息,如果显式指定pointcut值,它将覆盖value的设置值,可以将pointcut成员看成是value的同义词;
- throwing:将抛出的异常绑定到增强方法中;
- argNames:如前所述。
@After
Final增强,不管是抛出异常或者是正常退出,该增强都会得到执行,该增强没有对应的增强接口,可以把它看成是ThrowsAdvice和AfterReturningAdvice的混合物,一般用于释放资源,相当于try{}finally{}的控制流。
After注解类拥有两个成员:
- value:该成员用于定义切点;
- argNames:如前所述。
@DeclareParents
引介增强,相当于IntroductionInterceptor,
DeclareParents注解类拥有两个成员:
- value:该成员用于定义切点,它表示在哪个目标类上添加引介增强;
- defaultImpl:默认的接口实现类。
引介增强用法
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster
假设我们希望Waiter能够同时充当Seller的角色,即通过切面技术为NaiveWaiter新增Seller接口的实现。
下面我们使用@AspectJ的引介增强来实现这一个功能。
package com.xgj.aop.spring.advisor.aspectJ.basic; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclareParents; /** * * * @ClassName: EnableSellerAspect * * @Description: 希望Waiter也能同时充当Seller的角色 * * @author: Mr.Yang * * @date: 2017年8月26日 上午1:23:41 */ @Aspect public class EnableSellerAspect { // (1)value 为NaiveWaiter添加接口实现, (2)defaultImpl默认的接口实现类 @DeclareParents(value = "com.xgj.aop.spring.advisor.aspectJ.basic.NaiveWaiter", defaultImpl = SmartSeller.class) public Seller seller; // (3) 要实现的目标接口 }
分析:
在EnableSellerAspect 切面中,通过@DeclareParents为NaiveWaiter添加了一个需要实现的Seller接口,并指定其默认实现类为SmartSeller. 然后通过切面技术将SmartSeller融合到NaiveWaiter中,这样NaiveWaiter就实现了Seller接口。
配置文件,配置切面和NaiveWaiter Bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 使用基于Schema的aop命名空间进行配置 --> <!-- 基于@AspectJ切面的驱动器 --> <aop:aspectj-autoproxy/> <!-- 目标Bean --> <bean id="waiter" class="com.xgj.aop.spring.advisor.aspectJ.basic.NaiveWaiter"/> <!-- 使用了@AspectJ注解的切面类 --> <bean class="com.xgj.aop.spring.advisor.aspectJ.basic.EnableSellerAspect"/> </beans>
测试类:
package com.xgj.aop.spring.advisor.aspectJ.basic; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class EnableSellerAspectTest { @Test public void test() { ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath:com/xgj/aop/spring/advisor/aspectJ/basic/conf-aspectJ.xml"); Waiter waiter = ctx.getBean("waiter", Waiter.class); waiter.greetTo("XiaoGongJiang"); // 可以成功的进行强制类型转换 Seller seller = (Seller) waiter; seller.sell("beer", "XiaoGongJiang"); } }
运行结果:
2017-08-26 01:42:50,077 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@24b9371e: startup date [Sat Aug 26 01:42:50 BOT 2017]; root of context hierarchy 2017-08-26 01:42:50,173 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/aop/spring/advisor/aspectJ/basic/conf-aspectJ.xml] NaiveWaiter Greet to XiaoGongJiang SmartSeller: sell beer to XiaoGongJiang
可见,NaiveWaiter已经成功的新增了Seller接口的实现。