@AspectJ使用jdk5.0和正规的AspectJ切点表达式描述切面, 由于spring只支持方法的连接点,所以Spring只支持部分AspectJ的切点语言.
1.切点表达式函数
AspectJ 5的切点表达式有关键字和操作参数组成,如execution(* greetTo(..))的切点表达式, execution为关键字, "* greetTo(..)"为操作参数.在这里execution表示目标类执行某一方法, 而"* greetTo(..)"描述目标方法的匹配模式, 两者联合起来表示目标类greetTo()方法的连接点.为了描述方便, 将execution()称作为函数, 而将匹配串"* greetTo(..)"称为函数的入参.
Spring支持9个@AspectJ切点表达式函数, 它们用不同的方式描述目标类的连接点.根据描述对象的不同, 可以大致分为4种类型.
- 方法切点函数: 通过描述目标类方法信息定义连接点
- 方法入参切点函数: 通过描述目标类方法的入参信息定义连接点
- 目标类切点函数: 通过描述目标类类型信息定义连接点
- 代理类切点函数: 通过描述目标类的代理类的信息定义连接点.
这四种类型的切点函数, 通过下表进行详细说明:
类别 | 函数 | 入参 | 说明 |
方法切点函数 | execution() | 方法匹配模式 | 表示满足某一模式的所有目标类方法连接点, 如execution(* greetTo(..))表示所有目标类中的greetTo()方法 |
@annotation() | 方法注解类名 | 表示标注了特定注解的目标方法连接点, 如@annotation(com.bao.bao.aop.NeedTest)表示任何标注了@NeedTest注解的目标类方法 | |
方法入参切点函数 |
args() | 类名 | 通过判断目标类方法运行时入参对象的类型定义指定连接点.如args(com.bao.bao.Waiter)表示所有有且仅有一个按类型匹配于Waiter入参的方法 |
@args() | 类型注解类名 | 通过判断目标方法运行时入参对象的类型是否标注特定注解来指定连接点, 如@args(com.bao.bao.Monitorable)表示任何这样的一个目标方法, 它有一个入参且入参对象的类标注了@Monitorable注解 | |
目标类切点函数 | within() | 类名匹配器 | 表示特定域下的所有连接点.如within(co.bao.bao.service.*)表示com.bao.bao.service包中的所有连接点.即包中所有的类的所有方法, 而within(com.bao.bao.service.*Service)表示在com.bao.bao.service包中所有以Service结尾的类的所有连接点 |
target() | 类名 | 假如目标类按类型匹配于指定类, 则目标类的所有连接点匹配这个切点, 如通过target(com.bao.bao.Waiter)定义的切点, Waiter以及Waiter实现类NaiveWaiter中所有连接点都匹配该切点 | |
@within() | 类型注解类名 | 假如目标类按类型匹配于某个类A, 且类A标注了特定注解,则目标类的所有连接点匹配这个切点.如@within(com.bao.bao.Monitorable)定义的切点, 假如Waiter类标注了@Monitorable注解, 则Waiter以及Waiter实现类NaiveWaiter类的所有连接点都匹配 | |
@target() | 类型注解类名 | 目标类标注了特定注解, 则目标类所有连接点匹配该切点, 如@target(com.bao.bao.Monitorable).假如NaiveWaiter标注了@Monitorable, 则NaiveWaiter所有连接点匹配切点 | |
代理类切点函数 | this() | 类名 | 代理类按类型匹配于指定类, 则被代理的目标类所有连接点匹配切点. |
2.在函数入参中使用通配符
有些函数的入参可以接受通配符, @AspectJ支持3种通配符:
(1) * :匹配任意字符, 但它只能匹配上下文中的一个元素.
(2) ... : 匹配任意字符,可以匹配上下文中的多个元素, 但在表示类时, 必须和*联合使用, 而在表示入参时则单独使用.
(3) + :表示按类型匹配指定类的所有类, 必须跟在类名后面, 如com.bao.bao.Waiter+.继承或者扩展指定类的所有类, 同时还包括指定类本身.
@AspectJ函数按其是否支持通配符及支持的程度, 可以分成3类:
(1)支持所有通配符: execution(), within(), 如within(com.bao.bao.*), within(com.bao.bao.service.*Service+)等.
(2)仅支持+通配符: args(), this(), target(), 如args(com.bao.bao.Waiter+), target(java.util.List+)等.虽然这三个函数支持+通配符, 但是意义不大,因为对于这些函数来说, 使用和不适用+都是一样的.如target(com.bao.bao.Waiter)和targt(com.bao.bao.Waiter+)是等价的.
(3).不支持通配符:@args(), @within(), @target(), @annotation().如@args(com.bao.bao.NeedTest)和@within(com.bao.bao.NeedTest).
3.逻辑运算符
切点表达式由切点函数组成, 切点函数之间可以进行逻辑运算, 组成复合切点, Spring支持以下的切点运算符:
(1) && 与操作符:相当于切点的交集运算, 如果在Spring的xml配置文件中使用切点表达式, 由于&是xml特殊字符, 所以使用转义字符&&表示.为了使用方便, Spring提供了一个等效的运算符"and".如within(com.bao.bao..*) and args(String)表示在com.bao.bao包下所有类(当前包以及子孙包)拥有一个String入参的方法.
(2) || 或操作符, 相当于切点额并集运算, or是等效的操作符
(3) ! 非操作符: 相当于切点的反集运算, not是等效的操作符.
4.不同增强类型
在前面, 我们使用不同的接口描述各种增强,@AspectJ也为各种的增强类型提供了不同的注解类, 它们位于org.aspectj.lang.annotation.*包中, 这些注解类型拥有若干个成员,可以通过这些成员完成定义切点信息, 绑定连接点参数等操作.这些注解的存留期限都是RetentionPolicy.RUNTIME, 标注目标都是ElementType.METHOD.下面我们看下@AspectJ提供的几个增强注解.
(1) @Before
前置增强, 相当于BeforeAdvice的功能
(2) @AfterReturning
后置增强, 相当于AfterReturningAdvice的功能
(3) @Around
环绕增强, 相当于MethodInterceptor
(4) @AfterThrowing
抛出增强,相当于ThrowsAdvice
(5) @After
final增强,该增强没有对应的接口,但是可以把该增强看做是AfterReturningAdvice和ThrowsAdvice的混合物
(6) @DeclareParents
引介增强,相当于IntroductionInterceptor
下面给出引介增强的使用例子:
package com.bao.bao.aspectj; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclareParents; /** * Created by xinfengyao on 16-10-24. */ @Aspect public class EnableSellerAspect {
//为NaiveWaiter添加接口实现, 默认的实现类是SmartSeller @DeclareParents(value = "com.bao.bao.aspectj.NaiveWaiter", defaultImpl = SmartSeller.class)
//要实现的接口
public Seller seller; }
<bean id="waiter" class="com.bao.bao.aspectj.NaiveWaiter"/> <bean class="com.bao.bao.aspectj.EnableSellerAspect"/> <aop:aspectj-autoproxy/>
String config = "spring/spring-aspectj.xml"; ApplicationContext context = new ClassPathXmlApplicationContext(config); Waiter waiter = (Waiter) context.getBean("waiter"); waiter.serveTo("tom"); Seller seller = (Seller) waiter; seller.sell("book");
运行结果:
NaiveWaiter serveTo tom... smartSeller sell goods
可见, NaiveWaiter已经成功地新增了Seller接口的实现