AOP
对目标方法进行统一的日志、异常捕获等处理时,每个方法都要加上类似的代码。即繁琐也不利于重构。
代理模式解决
代理模式可以参考笔记“设计模式-代理模式”里面的说明。
下面提供使用代理模式提供输出日志的示例:
|
|
AOP简介
AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。AOP中经常使用到下面的术语:
- aspect(切面):被模块化的特殊对象。例如最常见的logging功能,一个logging切面就可以使用AOP来实现相同的功能了。
- jointpoint(连接点):进程执行的某个特定位置。连接点是应用进程提供给切面插入的地方。
- pointcut(切点): AOP 通过切点定位到特定的连接点。可以有这样的类比:连接点相当于数据库中的记录,切点相当于查询条件。
Spring AOP示例
Spring AOP使用AspectJ注解实现,具体步骤为:
(1):加入和AOP相关的jar包,包括:
|
|
(2):配置切面类
切面类是AOP编程中的核心类,它是一个Java类,这个类中的方法就是各种前置、后置、异常通知等方法。示例:
|
|
注意,一定要加上@Aspect和@Component两个注解,因为:
- @Aspect声明这是一个切面类;
- 声明为@Aspect的类会从自动代理排除(无法在IOC容器自动创建),所以需要加上@Component注解,以便加入到IOC容器中。
(3):配置通知方法
使用@Before
、 @After
、 @AfterReturning
、@AfterThrowing
和 @Around
等注解声明通知方法,后面还会详细介绍这些方法。示例:
|
|
(4):配置AOP并执行
前面已经提供了切面类和通知方法,首先下面是供测试使用的接口和接口的实现类:
|
|
注意,这里实现类用@Component标志,以便在IOC容器中自动创建。
此时可以使用XML配置文档打开AOP或者是零配置的注解方式,方法分别是:
- 使用XML需要配置2项内容,分别是自动扫描包和启动自动代理,所以此时的配置内容为:
|
|
测试类为:
|
|
可以看到这是前面常见的做法,即使用ClassPathXmlApplicationContext
获取IOC容器,而IOC容器通过扫描的方式创建Bean对象。这里CalculatorImpl实现类已经加上了@Component注解,所以可以从IOC容器中获取。
- 使用零配置的方式,需要用注解类来代替上面XML中的2项配置,所以是这样的:
|
|
这里简单说明一下:
+ @Configuration声明当前类是注解类,对应XML配置文档;
+ @ComponentScan声明扫描的包,对应XML配置中的`<context:component-scan base-package=""/>`;
+ @EnableAspectJAutoProxy(proxyTargetClass = true):开启自动代理,对应`<aop:aspectj-autoproxy /> `,注意proxyTargetClass 一定要设置为true,因为默认为false;
+ 最后用`AnnotationConfigApplicationContext`获取当前的类,创建IOC容器。在前面的笔记中介绍了这个类的使用。
通知种类
AspectJ 支持 5 种类型的通知注解:
- @Before:前置通知,在方法执行之前执行。通知方法可以接收
JoinPoint
类型参数,下同。
- @After:后置通知,在方法执行之后执行。
后置通知的特点是 无论目标方法是正常返回还是抛出异常,后置通知都会执行。所以后置通知 不能获取目标方法的返回值,因为不知道是否会执行异常;
- @AfterRunning:返回通知。
特点是正常执行结束后调用, 返回通知可以获取目标方法的返回值 。@AfterRunning
可以使用returning
属性声明返回值,作为通知方法的入参。
@AfterThrowing
可以使用throwing
声明捕获的异常,作为通知方法的入参。- @Around:环绕通知,可以自由选择实现前面的4个通知类型。
环绕通知方法的参数可以是ProceedingJoinPoint
类型,它是JoinPoint
的子类,提供了proceed()
方法执行目标方法,并且通知方法一定要有返回值。
示例:
|
|
切点表达式(pointcut expression)
前面说过,切点就是连接点的查询条件。在配置通知方法时,通过切点表达式匹配具体的目标方法,例如前面示例中的:
|
|
其中*
号表示匹配任意的public方法,*
号 还可以对访问权限和返回值类型进行通配。例如:
|
|
或者
|
|
如果要匹配任意数目任意类型的参数,可以使用..
。例如:
|
|
其实Spring AOP支持以下的标志符:
- execution - for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP
- within - limits matching to join points within certain types (simply the execution of a method declared within a matching type when using Spring AOP)
- this - limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type
- target - limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type
- args - limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types
- @target - limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type
- @args - limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given type(s)
- @within - limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP)
- @annotation - limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation
具体可以参考Supported Pointcut Designators
合并切点表达式
切点表达式可以直接在通知方法的注解中书写,但同一个切点表达式可能会在多个通知中重复出现。
此时可以通过@Pointcut
注解将一个切点声明成简单的方法,切点的方法体通常是空的。后面使用同样的该切点表达式的通知方法直接引用@Pointcut
注解的方法名即可。
示例:
|
|
示例中用@Pointcut(value = "execution(public int section04.aspect.Calculator.*(int, int))")
声明了切点方法,所以前置通知和后置通知中可以直接引用@Before(value = "pointCutMethod()")
。
切面的优先级
多个切面对同一个方法生效,这时候就有优先级问题。
使用注解@Order
声明切面的优先级,属性value的值越小,则优先级越高。
示例,下面的LoggingAspect的前置通知的优先级比LoggingAspect2 高:
|
|