第四章:面向切面的Spring
4.1、什么是面向切面编程
AOP可以实现横切关注点与它们所影响的对象之间的解耦。
1、描述切面常用的术语有:
通知(advice):
切面的工作被称为通知,通知定义了切面是什么以及何时调用。(AOP类的增强方法)
通知分分为五种类型:
·前置通知(Before):在目标方法调用前调用通知功能
·后置通知(After):在目标方法完成之后调用通知,不关心方法的输出是什么
·返回通知(After-rethrning):在目标方法执行成功后调用通知
·异常通知(After-throwing):在目标方法抛出异常时调用通知
·环绕通知(Around):通知包裹了被通知的方法,在调用前和后都执行自定义行为
连接点(Join point):
连接点是在应用执行过程中能够插入切面的一个点。(目标的所有方法等)
切点(pointcut):
切点定义了何处将要调用通知,匹配通知所要织入的一个或多个连接点。(实际选择的连接点)
切面(Pointcut):
切面是通知和切点的结合。
引入(Introduction):
引入允许向现有的类添加新方法和属性,使得在无需修改现有类的情况下让其具有新的行为和状态。
织入(Weaving):
把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。可以在编译期、类加载期、运行期进行织入。
2、Spring对AOP的支持
SpringAOP构建在动态代理基础上,提供4种类型的AOP支持:
·基于代理的经典SpringAOP(原始方法,过时)
·纯POJO切面
·@AspectJ注解驱动的切面
·注入式Aspect切面
Spring通知是使用标准Java类编写,定义通知所应用的切点一般会使用注解或XML配置。而AspectJ与之相反,以java语言扩展的方式实现。
Spring在运行期把切面织入到bean中,代理类封装了目标类,并拦截目标类中被通知方法的调用,拦截到调用时,先执行切面逻辑,后再将把调用转发给真正的目标bean。Spring在运行时才创建代理对象,不需要特殊的编译器来织入AOP切面。(即在bean创建时一并创建代理对象)
Spring是基于动态代理,只支持方法级别的连接点,不支持字段连接点和构造器连接点。如果需要可以使用Aspect来补充SpringAOP的功能。
4.2、通过切点来选择连接点
arg()限定参数类型
@args()限定指定注解类型
this()限定AOP代理的bean为指定类型的类
target限定为指定类型的类
@target()限定指定类且有指定注解
Within()限定匹配指定类型
@within()指定注解所标注的类型
@annotation带有指定注解的连接点
bean() 限制指定bean的ID
编写切点 execution执行(*代表任意返回值 …某方法 …表示任意参数)
即添加一个Around环绕通知,在setName方法执行的时候调用环绕通知逻辑。
4.3使用注解创建切面
1、定义切面
使用@Aspect注解在一个类上,表明这个类是一个切面,其中的方法(通知)都使用注解来定义切面的具体行为。
AspectJ提供了五种注解来定义通知类型
·@Before前置通知
·@After 后置通知 返回或异常通知
·@AfterReturning返回通知 返回才通知
·@AfterThrowing异常通知
·@Around环绕通知
可见,每个通知都是需要给定一个切点表达式作为值,可以定义切点表达式,减少重复书写的次数。使用@PointCut注解,设置一个切点表达式的值在一个方法上,其他通知定义时,引用此方法。
这个切面类中也可以有其他方法,也可以像平时一样调用这些被注解了通知的方法,因为其本质和普通类没有区别。
自此切面类已经编写完毕,如果要启动切面类,必须将切面类交给Spring管理(看上图,加了@Aspect,并且还需要@Component让Spring生成bean)。除此之外,必须在配置类的级别上使用@EnableAspectJAutoProxy,注解开启自动代理的功能。
此配置类中启动了扫描,并且允许AspectJ自动生成代理。
如果使用XML的配置方式,如下,同理也是开启扫描和开启aspectj的自动代理。
配置了自动代理后,Spring会为使用了@Aspect注解的bean创建一个代理。这个代理会围绕着所有该切面的切点所对应的bean。
注意:虽然使用了AspectJ的代理,但是仍然是Spring基于代理的切面,只能代理方法的调用。如果需要构造器/属性层面上的代理,必须使用AspectJ本身来创建切面。
2、编写环绕通知
注意上图的案例中,环绕通知并不会生效两次,原因就是环绕通知不是简单的在返回前后调用两次方法,而是通过接受ProceedingJoinPoint作为参数,选择何时将控制权交给被通知的方法,只调用一次方法,**环绕通知相当于可以同时应用前置/后置/异常通知。**案例如下:
Proceed()前的内容即前置通知,后的内容为后置通知,catch异常则为异常通知。可以不调用proceed(),阻塞对目标方法的调用,也可以在环绕通知中多次调用,实现重试。
3、处理通知中的参数
现如果我们需要使用到被通知的方法的参数,例如调用boss的drive()方法时,对drive的是什么车进行计数,可以编写如下的一个切面类。
同样用@Aspect注解一个类,@PointCut注解一个切点表达式:任意返回值的boss类的drive(String)方法,值得注意的是,因为bossDriveCar(String carName)中传入了一个carName,
为了接收到这个carName传入通知,必须使用args()选择器,其值必须和方法的形参相同,否则会编译出错。同理@Before中的参数也必须和下面的形参相同。
args(carName)限定符:传递给drive的String参数要传递到通知bossDriveCar的形参carName
效果如下:说明可以读到传递给被通知方法的参数。
4.4在XML中声明切面
上面展示了如何在JavaConfig进行注解的切面,也可以在XML中进行切面配置。在Spring的AOP命名空间中,提供了多个元素 在XML声明切面。
·aop:advisor 定义通知器
·aop:before 定义前置通知
·aop:after 定义后置通知
·aop:after-returning 定义返回通知
·aop:after-throwing 定义异常通知
·aop:around 定义环绕通知
·aop:aspect 定义一个切面
·aop:aspectj-autoproxy 启动@AspectJ注解驱动的切面
·aop:config 顶层的AOP配置元素
·aop:pointcut 定义切点
·aop:declare-parents 以透明的方式被通知的对象引入额外的接口
此时把carServiceAop的所有注解都去掉,使用XML配置如下
可见,与之前的基于config的注解配置方式都是一一对应的,可以查看上面的图再仔细对比。
现在来看之前的另外一个例子:
同理,也是一一对应,但是&&被改为了and。