Spring注解驱动开发系列:
Spring注解驱动开发之AOP
面向切面编程,不改变原有代码的前提下,进行功能增强。
@EnableAspectJAutoProxy
在配置类上加上此注解,开启增强类的自动代理
@Aspect
标在类上,声明这是一个增强类
具体的切入点注解
@Before标在方法上,在被增强方法执行前调用
其中,传入切入点表达式
相似的还有@After、@AfterReturning、@AfterThrowing、@Around,分别在方法执行后(无论正常结束还是异常退出),方法正常执行返回后,方法出现异常后,而@Around可以自由的在方法执行前后切入
具体的细节请看示例
首先,模拟一个已有方法
@Component
public class DivTest {
public int div(int i, int j){
System.out.println("DivTest...div()被调用了");
return i/j;
}
}
我们先要增强这个方法,随后进行了一系列操作
- 在配置类上加上注解@EnableAspectJAutoProxy,开启功能
- 在增强类上标注@Aspect
- 编写具体的增强类
// 声明这是一个增强类
@Aspect
@Component
public class DivAspect {
// 抽取公共部分
@Pointcut("execution(public int south.block.aop.DivTest.div(..))")
public void pointCut(){}
// 可以使用切入点表达式,也可以使用@Pointcut标注的方法
@Before("execution(public int south.block.aop.DivTest.div(..))")
public void beforeDiv(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
Object pointThis = joinPoint.getThis();
System.out.println( "beforeDiv>>>>>>>" + pointThis + "." + name + Arrays.toString(args) );
}
@After("pointCut()")
public void afterDiv(){
System.out.println("afterDiv>>>>>>>>>");
}
// 可以使用returning属性指定方法的返回值,Spring会给被指定的形参赋好值
@AfterReturning(value = "pointCut()",returning = "result")
// 需要注意的是:当条件中有joinPoint时,需要将其放在形参的首位,否则报错 Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut
public void afterReturingDiv(JoinPoint joinPoint, Object result ){
System.out.println("afterReturingDiv>>>>>>>>" + joinPoint.getSignature().getName() + "方法正常结束,返回值是" + result);
}
// 类似的,也可以用throwing属性指定异常,Spring会自动给形参赋值
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void afterThrowingDiv(JoinPoint joinPoint, Exception exception){
System.out.println("afterThrowingDiv>>>>>>>>" + joinPoint.getSignature().getName() + "方法出现了" + exception);
}
@Around("pointCut()")
// 注意使用的是ProceedingJoinPoint,而不是JoinPoint
// 另外,也要注意:方法的返回值要和被增强方法一样
public int arroundDiv(ProceedingJoinPoint joinPoint) throws Throwable {
String name = joinPoint.getSignature().getName();
System.out.println("arroundDiv " + name + " joinPoint.proceed前>>>>>>>>>");
Integer result = (Integer)joinPoint.proceed();
System.out.println("arroundDiv " + name + " joinPoint.proceed后>>>>>>>>>");
return result;
}
}
完成了这三步后,我们从容器中获取DivTest的实例对象调用div方法时,就会得到了增强
执行结果:
正常结束
arroundDiv div joinPoint.proceed前>>>>>>>>>
beforeDiv>>>>>>>south.block.aop.DivTest@3e96bacf.div[1, 1]
DivTest...div()被调用了
arroundDiv div joinPoint.proceed后>>>>>>>>>
afterDiv>>>>>>>>>
afterReturingDiv>>>>>>>>div方法正常结束,返回值是1
异常结束
arroundDiv div joinPoint.proceed前>>>>>>>>>
beforeDiv>>>>>>>south.block.aop.DivTest@3e96bacf.div[1, 0]
DivTest...div()被调用了
afterDiv>>>>>>>>>
afterThrowingDiv>>>>>>>>div方法出现了java.lang.ArithmeticException: / by zero
可以看到,各种通知都有的情况下,正常结束执行顺序为:
@Around环绕通知的前置部分
@Before
被增强方法
@Around环绕通知的后置部分
@After
@AfterReturing
出现异常情况的顺序:
@Around环绕通知的前置部分
@Before
被增强方法
@After
@AfterThrowing
注意点:
- 不要忘记标注@EnableAspectJAutoProxy开启功能
- 不要忘记标注@Aspect注解声明增强类
- 不要忘记将增强类和被增强类的实例加入容器
- 在增强方法中,如果使用了JoinPoint类的形参,要将JoinPoint形参放在方法的参数列表中的第一位
- 在@Around方法中形参是ProceedingJoinPoint,区别于其他方法的JoinPoint
- 如果需要指定返回值和抛出的异常,不要忘记在注解中使用相关属性指定
- 要想使用增强的方法,必须从容器中获取相关的bean,而不是手动new Xxx();
源码分析
@EnableAspectJAutoProxy
@EnableAspectJAutoProxy注解作为AOP的一个开关,我们从他开始分析
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
}
值得注意的是,利用@Import(AspectJAutoProxyRegistrar.class),引入了AspectJAutoProxyRegistrar注册器
我们再分析一下AspectJAutoProxyRegistrar
AspectJAutoProxyRegistrar
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
}
AspectJAutoProxyRegistrar会自动调用registerBeanDefinitions()方法
进入查看AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)方法,
发现依次调用registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source)方法
及registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source)方法
查看registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source)方法代码:
至此,容器中已经注册好了名为internalAutoProxyCreator,类型为AnnotationAwareAspectAutoProxyCreator的组件,后面只是一些设置,暂不探讨。
AnnotationAwareAspectAutoProxyCreator
我们继续分析AnnotationAwareAspectAutoProxyCreator类
查看下AnnotationAwareAspectAutoProxyCreator的继承树
在AbstractAutoProxyCreator类中,发现该类实现了SmartInstantiationAwareBeanPostProcessor接口,而该接口继承于BeanPostProcessor。BeanPostProcessor其中定义了初始化前后执行的两个方法,详情请看Spring Bean的生命周期。XxxAware接口是可以在初始化时将Spring底层Xxx组件注入到别的组件中,详情请看Spring注解实现赋值与自动装配。
AbstractAutoProxyCreator类中实现了setBeanFactory()方法,但是又被子类重写了
我们跳转至子类AbstractAdvisorAutoProxyCreator的setBeanFactory()方法,在其中调用了initBeanFactory()方法,而且该方法被子类重写了
我们继续跳转至AnnotationAwareAspectJAutoProxyCreator类中的initBeanFactory()方法
分析得知:AnnotationAwareAspectJAutoProxyCreator会调用AbstractAdvisorAutoProxyCreator定义的setBeanFactory()方法,在该方法中又会调用AnnotationAwareAspectJAutoProxyCreator类定义的initBeanFactory()方法。我们在这两方法处打上断点,在配置类中创建MathCalculator类bean对象和LogAspects类bean对象处打上断点,开始debug分析。
我们需要重点分析AnnotationAwareAspectAutoProxyCreator的注册,因为它是Spring实现AOP的关键。
这里先说一下大致流程,下面再分析源代码
1)、传入配置类、创建IOC容器
2)、注册配置类,刷新容器
3)、注册Bean的后置处理器
3.1)、获取IOC容器中已经定义的需要创建对象的后置处理器
3.2)、添加一些别的后置处理器
3.3)、将后置处理器按优先级分成PriorityOrdered、Ordered、其他类
3.4)、先后注册这些后置处理器
3.4.1)、createBean
3.4.1.1)、resolveBeforeInstantiation执行实例化之前的Bean后置处理方法
3.4.1.2)、创建Bean的实例
3.4.2)、populateBean 给属性赋值
3.4.3)、initializeBean 初始化Bean
3.4.3.1)、invokeAwareMethods 调用XxxAware接口中定义的方法,将Xxx注入当前bean
3.4.3.2)、applyBeanPostProcessorsBeforeInitialization 执行初始化前的后置处理方法
3.4.3.3)、invokeInitMethods 执行初始化方法
3.4.3.4)、applyBeanPostProcessorsAfterInitialization 执行初始化后的后置处理方法
3.4.4)、BeanPostProcessor 创建成功
3.5)、把BeanPostProcessor注入容器中
4)、finishBeanFactoryInitialization(beanFactory) 完成BeanFactory初始化工作;创建剩下的单实例bean
4.1)、获取容器中已经定义了的所有的bean
4.2)、判断是否单例、是否抽象、是否懒加载
4.3)、如果是抽象单例懒加载,就getBean()–>doGetBean()
4.3.1)、先在缓存中检查此bean是否已经被手动注册
4.3.2)、缓存中有就直接使用
4.3.3)、没有则继续创建createBean()
4.3.3.1)、resolveBeforeInstantiation()实例化前解析工作,尝试返回一个代理对象
4.3.3.2)、doCreateBean 创建bean对象,此处过程就和3.4)过程类似了,此处不再说明
分析一下方法栈:
我们发现容器已经创建好了
此时,开始注册BeanPostProcessor,程序运行至我们打的断点处
发现internalAutoProxyCreator应在orderedPostProcessorNames中
咱们给程序放行,程序来到AbstractAdvisorAutoProxyCreator类的setBeanFactory方法,说明了程序已经在初始化internalAutoProxyCreator了
继续放行,程序来到了AnnotationAwareAspectJAutoProxyCreator类的initBeanFactory方法。方法执行结束后,Factory已成功注入到internalAutoProxyCreator中了。
逐行运行,查看方法栈(这也是后面创建其他Bean的过程)
我们先看createBean方法:
其中的有调用实例化前后的后置处理方法和bean的创建
我们查看resolveBeforeInstantiation方法,发现实现了InstantiationAwareBeanPostProcessor接口的后置处理器可以被调用实例化前的后置处理方法,而我们的AnnotationAwareAspectJAutoProxyCreator类实现的SmartInstantiationAwareBeanPostProcessor接口继承于InstantiationAwareBeanPostProcessor接口。之后bean的创建都会调用AnnotationAwareAspectJAutoProxyCreator的实例化前后的后置处理方法。当然!internalAutoProxyCreator自身的创建并不会调用。
按照方法栈,接着查看doCreateBean方法:
按照方法栈,查看AbstractAutowireCapableBeanFactory类的initializeBean方法
此方法最终返回一个bean的包装对象,再经过一系列返回,最终到达PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors方法中,将刚创建的internalAutoProxyCreator组件注册进容器中。至此,AnnotationAwareAspectJAutoProxyCreator成功创建并注入容器中。
随后,经过相似的过程,将其他的PostProcessor注入容器中
Spring在AnnotationConfigApplicationContext构造器中的refresh()方法中调用接着完成BeanFactory的初始化工作,创建剩下的单实例非懒加载的bean
我们继续分析其他普通bean的创建过程,从这开始AnnotationAwareAspectJAutoProxyCreator就开始生效了。
由于我们需要分析AOP的过程,所以我们比较关心MathCalculator业务逻辑类和LogAspects切面类的创建过程。
直接将程序运行至断点处
分析方法栈:
从preInstantiateSingletons开始分析
getBean中直接调用doGetBean方法,我们直接看doGetBean方法
继续查看createBean方法
真正的创建Bean接下来的过程就和AnnotationAwareAspectJAutoProxyCreator类似了,我们不再讨论。
真正的AOP
我们现在可以开始进入对AOP过程的探讨
主要关心三个问题:
- AnnotationAwareAspectJAutoProxyCreator有什么用
- 代理对象是怎么创建的
- 增强方法调用的过程
针对这三个问题,我们重新打上断点
第一处:为了分析业务逻辑类和切面类实例化前,AnnotationAwareAspectJAutoProxyCreator对其的处理
第二处:为了分析业务逻辑类和切面类初始化后,AnnotationAwareAspectJAutoProxyCreator对其的处理
第三处:为了分析增强方法被调用时的具体过程
在正式分析前,我们先总的来梳理一下:
- AnnotationAwareAspectJAutoProxyCreator有什么用
- AnnotationAwareAspectJAutoProxyCreator会在bean创建之前,调用postProcessBeforeInstantiation();
- 判断是否已经增强
- 判断是否是”基础类型“或被@Aspect 注解标注
- 判断是否有TargeSource目标源
- bean创建出来后,还有执行初始化后的后置处理方法
- 代理对象是怎么创建的(在初始化后的后置处理方法中包装成代理对象)
- return wrapIfNecessary(bean, beanName, cacheKey)
- 获取到当前bean所有的增强器
- 保存当前bean到advicedBean中
- 如果需要增强,创建代理对象
- 给容器中返回当前组件使用cglib增强的代理对象
- 以后容器中获取的就是这个bean的代理对象
- 增强方法调用的过程
- CglibAopProxy.intercept();拦截目标方法的执行
- 根据ProxyFactory对象获取将要执行的目标方法拦截器链
- 如果没有拦截器链,直接执行目标方法;
- 如果有拦截器链,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个CglibMethodInvocation 对象,并调用 Object retVal = mi.proceed();链式调用拦截器或被增强方法
现在开始调试
首先来到了实例化前后置处理方法处
具体分析一下上图中的:判断是否是“基础类型”,这里我们的bean是业务逻辑类,并不是切面类,所以不是@Aspect注解修饰的。
判断是否需要跳过
实例化前后置处理方法最后返回null,将执行初始化方法,我们放行来到了初始化后的后置处理方法
我们进一步分析:
我们先分析获取增强器的具体过程:
我们接着分析代理对象的创建过程
再下一层:
逐层返回后,代理对象就创建好了。以后就是使用代理对象去代替原来的对象了
接着,我们来分析增强方法的调用过程:
放行至调用div方法处
进入发现,程序被CglibAopProxy.intercept()拦截
获取到拦截器链后,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个 CglibMethodInvocation 对象。随后开始链式调用
注意:第一次的proceed方法和后面的proceed方法各不相同,他们是不同类下的同名方法。看似是循环,实则是链
调用最后一个拦截器时,就会开始调用增强方法(或者被增强方法了),然后返回,再从下而上地依次调用增强方法(或被增强方法)
细心的小伙伴可能会发现,下图中的拦截器的顺序和实际的执行顺序不同。
那是因为,调用到环绕增强时,和其他的增强方法不同,他的过程比较复杂
下图中,环绕通知方法已经i开始执行,但是其中的joinpoint.proceed()方法会被前置增强的拦截器拦截。所以,环绕通知的前一部分先执行,随后执行@before方法,@before方法运行完出栈后,环绕通知方法中的joinpoint.proceed()就执行(目标方法执行),随后,环绕通知方法后半部分执行。最后,依次放行给方法栈中下面的拦截器。
到这里,AOP过程基本上就实现了。
参考链接:
尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)
如果此博客对你有帮助,请帮忙点点关注,点赞呗!谢谢啦!