四、Spring AOP
AOP的常用术语:
-
通知:
- Before:在方法被调用前通知
- After:在方法完成之后调用通知,无论方法执行是否成功
- After-returning:在方法成功执行之后调用通知
- After-throwing:在方法抛出异常后调用通知
- Around:通知包裹了被通知的方法,在被通知方法调用之前和之后执行自定义的行为。
- 连接点:
- 切点:
- 切面:
- 引入:
-
织入:
- 编译期:切面在目标类编译时被织入,这种方式需要特殊的编译器。
- 类加载期:切面在目标类加载到JVM时被织入,这种方式需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标的字节码。
- 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态的创建一个代理对象。
AOP框架
并不是所有的AOP框架都是一样的,它们在连接点模型上可能有强弱之分。有些允许对字段修饰符级别应用通知,而另一些只支持与方法调用相关的连接点。它们织入切面的方式和时机也有所不同。但是无论如何,创建切点来定义切面织入的连接点是AOP框架的基本功能。
三足鼎立的AOP框架:
- AspectJ
- JBoss AOP
- Spring AOP
Spring AOP
Spring提供了4种各具特色的AOP支持:
- 基于代理的经典AOP
- @AspectJ注解驱动的切面
- 纯POJO切面
- 注入式AspectJ切面
前三种都是Spring基于代理AOP的变体,因此,Spring对AOP的支持局限于方法拦截。如果AOP需求超过了简单方法拦截的范畴,那么应该考虑在AspectJ里面实现,利用Spring的DI把Spring Bean注入到AspectJ切面中。
使用切点选择连接点
切点是用于准确定位应该在什么地方应用切面的通知。切点个通知是切面的最基本元素。
在Spring AOP中,需要使用AspectJ的切点表达式语言来定义切点。关于Spring AOP的AspectJ切点,最重要的一点是Spring仅支持AspectJ切点指示器的一个子集。
AspectJ指示器 | 描述 |
---|---|
arg() | 限制连接点匹配参数为指定类型的执行方法 |
@args() | 限制连接匹配参数由指定注解标注的执行方法 |
execution() | 用于匹配是连接点的执行方法 |
this() | 限制连接点匹配AOP代理的Bean应用类型为指定类型的类 |
target() | 限制连接点匹配目标对象为指定类型的类 |
@target() | 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解 |
within() | 限制连接点匹配指定的类型 |
@within() | 限制连接点匹配指定注解所标注的类型 |
@annotation | 限制匹配带有指定注解连接点 |
注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器。在此基础上,我们使用其他指示器来限制所匹配的切点。
编写切点
- execution
- 语法:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下:
modifiers-pattern
:方法的可见性,如public,protected;
ret-type-pattern
:方法的返回值类型,如int,void等;
declaring-type-pattern
:方法所在类的全路径名,如com.spring.Aspect;
name-pattern
:方法名类型,如buisinessService();
param-pattern
:方法的参数类型,如java.lang.String;
throws-pattern
:方法抛出的异常类型,如java.lang.Exception;
由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。
- 用例:
/*匹配使用public修饰,返回值为任意类型,并且是*com.spring.BusinessObject类中名称为businessService的方法,方法可以有多个参数,但是第一个参数必须是java.lang.String类型的方法*/
execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))
/*返回值为任意类型,在com.spring.service.BusinessObject类中,并且参数个数为零的方法*/
execution(* com.spring.service.BusinessObject.*())
/*返回值为任意类型,在com.spring.service包中,以Business为前缀的类,并且是类中参数个数为零方法:*/
execution(* com.spring.service.Business*.*())
/*匹配返回值为任意类型,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,而且该方法不能有任何参数*/
execution(* com.spring.service..*.businessService())
通配符
*通配符:该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。
..通配符:该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。
- within
- 语法:
within(declaring-type-pattern)
within表达式的粒度为类,其参数为全路径的类名(可使用通配符),表示匹配当前表达式的所有类都将被当前方法环绕。
- 用例:
/*匹配com.spring.service.BusinessObject中的所有方法*/
within(com.spring.service.BusinessObject)
/*匹配com.spring.service包下的所有类,不包括子包中的类*/
within(com.spring.service.*)
/*匹配com.spring.service包及子包下的所有类*/
within(com.spring.service..*)
- args
- 语法:
args(param-pattern)
args表达式的作用是匹配指定参数类型和指定参数数量的方法,无论其类路径或者是方法名是什么。这里需要注意的是,args指定的参数必须是全路径的。
- 用例:
/*匹配所有只有一个参数,并且参数类型是java.lang.String类型的方法*/
args(java.lang.String)
/*匹配第一个参数为java.lang.String,最后一个参数为java.lang.Integer,并且中间可以有任意个数和类型参数的方法*/
args(java.lang.String,..,java.lang.Integer)
- @within
- 语法:
@within(annotation-type)
前面我们讲解了within的语义表示匹配指定类型的类实例,这里的@within表示匹配带有指定注解的类
- 用例:
/*匹配使用com.spring.annotation.BusinessAspect注解标注的类*/
@within(com.spring.annotation.BusinessAspect)
- @annotation
- 语法:
@annotation(annotation-type)
表示匹配使用@annotation指定注解标注的方法将会被环绕
- 用例:
/*匹配使用com.spring.annotation.BusinessAspect注解标注的方法*/
@annotation(com.spring.annotation.BusinessAspect)
- @args
- 语法:
@args(annotation-type)
@within和@annotation分别表示匹配使用指定注解标注的类和标注的方法将会被匹配,@args则表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配。
- 用例:
/*匹配使用了com.spring.annotation.FruitAspect注解标注的类作为参数的方法*/
@args(com.spring.annotation.FruitAspect)