Spring Aop
Spring Aop 核心概念
- Aspect: 切面是包含了多个切点,事务管理器(Transaction management)就是一个很好的例子,通常在 spring aop 中通过 @Aspect 来申明
- Join point: 连接点,表示具体的方式执行的一个点,异常处理或者方法执行。
- Advice: 通知,在特定的连接点才去操作,比如在通知之前,通知之后,通知过程中增加自己的逻辑。
- Pointcut: 切点,表示我们想再那个具体的方法,那个具体或者匹配一类 "select" 开头的方法。这里支持 EL 表达式进行匹配。
- Introduction: 引入,表示 AOP 可以在代理过程中,将一个已有的类引入新的接口。
- Target object: 当前对象,其实也是原始对象,如果发生 AOP 代理的时候,此时返回的始终是 AOP 代理对象。
- AOP proxy: AOP 创建的代理对象。
- Weaving: 切面植入时机,可以是运行时,运行前,运行后。
Spring AOP 包括以下几个类型的通知(Advice):
前置通知:在连接点之前运行的通知,但是它不能阻止执行流程前进到连接点(除非它引发异常)。
后置通知:在连接点正常完成之后要运行的通知(例如,如果方法返回而没有引发异常)。
异常通知:如果方法因引发异常而退出,则运行通知。
建议(最终)之后:无论连接点退出的方式如何(正常或特殊收益),均应执行通知。
环绕通知:围绕连接点的通知,例如方法调用。这是最有力的通知。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是返回连接点还是通过返回其自身的返回值或引发异常来结束方法的执行
环绕通知是最通用的通知。由于 Spring AOP 与 AspectJ 一样,提供了各种通知类型,建议使用功能最弱的建议类型,以实现所需的行为。例如,如果您只需要使用方法的返回值更新缓存,则最好使用后置通知而不是环绕通知,尽管环绕通知可以完成相同的事情。使用最具体的通知类型可以提供更简单的编程模型,并减少出错的可能性。例如,您不需要调用用于around通知的proceed() 方法JoinPoint,因此不会导致主业务失败。
所有建议参数都是具体的类型,因此您可以使用适当类型(例如,方法执行的返回值的类型)而不是Object数组的建议参数。
切入点匹配的连接点的概念是 AOP 的关键,它与仅提供拦截功能的旧技术有所不同。切入点使建议的目标独立于面向对象的层次结构。例如,可以将提供声明性事务管理的环绕建议应用于跨越多个对象(例如服务层中的所有业务操作)的一组方法。
Spring Aop 实现概述
Spring AOP默认将标准JDK动态代理用于AOP代理。这使得可以代理任何接口(或一组接口)。
Spring AOP也可以使用CGLIB代理。这对于代理类而不是接口是必需的。默认情况下,如果业务对象未实现接口,则使用CGLIB。由于最好是对接口而不是对类进行编程,因此业务类通常实现一个或多个业务接口。在某些情况下(可能极少数),当您需要建议未在接口上声明的方法或需要将代理对象作为具体类型传递给方法时,可以强制使用 CGLIB。
总结: Spring AOP 默认是采用 JDK 动态代理,通常情况下,如果一个类没有实现接口那么就是用 GCLIB 代理
Spring Aop 常用注解
1. 启动 @AspectJ支持
要在Spring配置中使用@AspectJ切面,您需要启用Spring支持以基于@AspectJ方面配置Spring AOP,并基于这些切面是否触发通知对Bean进行自动代理。通过自动代理,我们的意思是,如果Spring确定一个或多个切面通知使用bean,它将自动为该bean生成一个代理以拦截方法调用并确保按需运行建议。
可以使用XML或Java样式的配置来启用@AspectJ支持。无论哪种情况,您都需要确保AspectJ的aspectjweaver.jar库位于应用程序的类路径(版本1.8或更高版本)上。
代码例子:
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
2. 声明一个切面
启用@AspectJ支持后,@Aspect Spring会自动检测到在应用程序上下文中使用@AspectJ方面(具有注释)的类定义的任何bean,并用于配置Spring AOP。下面是一个声明切面的例子
代码例子:
@Aspect public class LogAspect { }
3. 声明一个切入点
切入点确定了拦截的连接点,从而使我们能够控制运行建议的时间。Spring AOP仅支持Spring Bean的方法执行连接点,因此您可以将切入点视为与Spring Bean上的方法执行相匹配。切入点声明由两部分组成:一个包含名称和任何参数的签名,以及一个切入点表达式,该切入点表达式精确地确定我们感兴趣的方法执行。在AOP的@AspectJ批注样式中,常规方法定义提供了切入点签名。 并通过使用@Pointcut
注解定义切入点表达式(用作切入点签名的方法必须具有void返回类型)。
一个例子:
@Pointcut("execution(* cn.edu.xxx.service.*.*(..))") public void serviceOperation() { }
Spring AOP支持以下在切入点表达式中使用的AspectJ切入点指示符:
- execution:用于匹配方法执行的连接点。这是使用Spring AOP时要使用的主要切入点指示符。
- within:将匹配限制为某些类型内的连接点(使用Spring AOP时,在匹配类型内声明的方法的执行)。
- this:限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中bean引用(Spring AOP代理)是给定类型的实例。
- target:在目标对象(代理的应用程序对象)是给定类型的实例的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。
- args:将匹配限制为连接点(使用Spring AOP时方法的执行),其中参数是给定类型的实例。
- @target:在执行对象的类具有给定类型的注释的情况下,将匹配限制为连接点(使用Spring AOP时方法的执行)。
- @args:限制匹配的连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。
- @within:将匹配限制为具有给定注释的类型内的连接点(使用Spring AOP时,使用给定注释的类型中声明的方法的执行)。
- @annotation:将匹配点限制为连接点的主题(在Spring AOP中运行的方法)具有给定注释的连接点。
4. 定义通知
议与切入点表达式关联,并且在切入点匹配的方法执行之前,之后或周围运行。切入点表达式可以是对命名切入点的简单引用,也可以是就地声明的切入点表达式。
在同一个定义的通知方法@Aspect
的类,需要在同一连接点运行基于分配优先级上按以下顺序他们的通知类型,从最高到最低的优先级:@Around
,@Before
,@After
, @AfterReturning
,@AfterThrowing
。但是请注意,由于Spring的实现风格,在同一方面中的any或advice方法之后AspectJAfterAdvice
,@Afteradvice
方法将被有效地调用。@AfterReturning
和 @AfterThrowing
当在同一类中@After
定义的两个相同类型的建议(例如,两个建议方法)@Aspect
都需要在同一连接点上运行时,其顺序是不确定的(因为无法通过以下方式检索源代码声明顺序) Javac编译类的反射)。请考虑将此类建议方法折叠为每个@Aspect
类中每个连接点的一个建议方法,或将这些建议重构为单独的@Aspect
类,您可以通过Ordered
或 在方面级别进行排序@Order
。
定义通知的例子:
@Before("cn.edu.cqvie.aspect.LogAspect.serviceOperation()") public void doServiceCheck() { System.out.println("doServiceCheck ....."); } @After("cn.edu.cqvie.aspect.LogAspect.serviceOperation()") public void doReleaseLock() { System.out.println("doReleaseLock ....."); } @AfterReturning( pointcut="cn.edu.cqvie.aspect.LogAspect.serviceOperation()", returning="retVal") public void doServiceCheck(Object retVal) { System.out.println("doServiceCheck ....." + retVal); }
5. 引入
简介(在AspectJ中称为类型间声明)使方面可以声明建议对象实现给定的接口,并代表那些对象提供该接口的实现。 您可以使用 @DeclareParents
进行引入。此批注用于声明匹配类型具有新的父类(因此具有名称)。
Spring Aop 核心原理
前面我们分析了 Spring AOP 的使用方法和基础概念,下面我们来继续分析一下 Spring AOP 的原理。
AnnotationAwareAspectJAutoProxyCreator AOP 后置处理器
Spring 再初始化 bean 的过程中,完成初始化过后,就执行 AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization
来执行代理,所以,代理的时机是在 Spring 初始化完成之后,并且在将 Bean
完整对象放入 Spring Bean
容器之前其目的就是将 Bean
的原始对象 BeanWapper
转换为一个代理对象。执行的过程如下:
- 先判断当前的 bean 是不是要进行AOP,比如当前的Bean的类型是 Pointcut, Advice, Advisor 等那些就不需要 AOP。
- 如果匹配到 Advisors 不为 null, 那么进行代理并且返回代理对象。
- 判断代理实现如果实现如果没有设置为 GCLIB 并且实现了接口,那么就采用 JDK 代理,否则使用 GCLIB。
- 执行代理的代码。
ProxyFactory 代理工厂
ProxyFactory 是一个代理工厂类,我们在使用 AOP 代理之前首先需要通过代理工厂获取一个具体的 AOP 代理对象 AopProxy 的实例,Spring 的源码如下:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // config 就是 ProxyFactory 对象 // optimize为 true,或 proxyTargetClass 为 true,或用户没有给 ProxyFactory 对象添加 interface if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } // targetClass是接口,直接使用Jdk动态代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 使用Cglib return new ObjenesisCglibAopProxy(config); } else { // 使用Jdk动态代理 return new JdkDynamicAopProxy(config); } }
在这里我们需要关注两个类 JdkDynamicAopProxy
, ObjenesisCglibAopProxy