Spring源码学习笔记13——总结篇, 从IOC到AOP
阅读原文时间:2023年08月18日阅读:1

系列文章目录和关于我

《Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点》中,我们总结了Spring IOC部分的知识,为了更好的给群里的伙伴们分享Spring AOP的知识,遂有了这篇文章,这篇文章将从IOC聊到AOP,其中IOC不会那么细致,重点还是在AOP。

1.AOP概述

  • AOPAspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 是一种新的模块化机制,用来描述分散在对象,类,或函数中的横切关注点

  • AOP的优点:分离关注点使解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑代码中不在含义针对特定领域的代码调用,业务逻辑同特定领域问题的关系通过切面封装,维护,这样原本分散在整个应用程序中的变动可以很好地管理起来

2.何为横切关注点

如上图中我们有三个Service处理不同的业务逻辑,但是在三个服务中有一些和业务无关的逻辑横切在业务代码中,比如上面的权限校验,事务,耗时统计等。

为了解决横切关注点和业务逻辑的耦合,以及其分散不好管理的问题,AOP营运而生。AOP解决问题的方式有三种:

  1. 编译期AOP:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect定义切面 + aspecj特殊编译器来实现编译期AOP;
  2. 类加载器AOP:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
  3. 运行期AOP:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。

其中Spring AOP就是基于运行期AOP。

3.Spring AOP

AOP 并不是Spring框架提出的概念,而是spring将AOP结合到自己的IOC框架中,在spring 的bean的声明周期中实现运行期AOP增强。代表的有Spring事务,Spring @Async,Spring @Cacheable等功能。

上面我们说到,Spring AOP的优势在于其基于Spring IOC在spring bean声明周期中对bean进行增强,从而实现:程序员编写横切逻辑=>Spring 容器启动 => 动态代理AOP=> 丝滑使用。

这一章节我们先简单回顾下Spring bean的生命周期,了解Spring AOP 实现的"抓手doge"

1. BeanDefinition的生成

注解 or xml,这是spring中定义bean两种常用方式,两种方式都最终会将bean的定义转化为Bean的定义信息BeanDefinition。

2.bean的实例化

BeanDefinition 将指导bean的生命周期,首先是实例化,即将BeanDefinition 中记录的类实例化成对象,

  • 其中根据Bean 作用域不同的有所不同

    • 单例bean且非懒加载:spring 容器启动的时候即初始化
    • 非单例bean:由对应的Scope控制生命周期,但是实例化和单例一样
  • 另外spring中具备多种实例化bean的方式

  • FactoryBean,调用getObject生成,一般是懒加载的,但是SmartFactoryBean#isEagerInit = true 且如果是单例的话,那么在spring启动的时候就会调用getObject进行生成

  • 非FactoryBean:

    • 反射调用构造方法or工厂方法 生成
    • CGLIB 生成子类进行生成(当使用了spring的MethodOverride后)

3.bean属性填充

bean的属性填充笔者不那么专业的分为三种

  • 有参构造方法 or 工厂方法实例化bean的时候,参数会被spring 进行依赖注入,从而我们可以将注入的bean设置到属性上

  • xml定义的bean使用<property>指定属性填充,or BeanDefinition#setPropertyValues 指定属性填充,这种方式将在populateBean进行属性填充

    GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
    MutablePropertyValues propertyValues = new MutablePropertyValues();
    // 指定属性a 使用 名称为b 进行填充
    propertyValues.add("a",new RuntimeBeanReference("b"));
    genericBeanDefinition.setPropertyValues(propertyValues);
  • @Resource 或者@Autowired注解定义的属性注入

    二者会由不同的BeanPostProcessor(Bean的后置处理器)进行处理

    @Resource由 CommonAnnotationBeanPostProcessor进行处理

    @Autowired由 AutowiredAnnotationBeanPostProcessor进行处理

4.bean的初始化

  1. 如果实现了Aware那么进行回调

    • BeanNameAware,BeanClassLoaderAware,BeanFactoryAware由BeanFactory#invokeAwareMethods方法直接触发
    • EnvironmentAware,ApplicationContextAware等接口,由ApplicationContextAwareProcessor这个BeanPostProcessor触发
  2. 回调初始化方法

    • 如果实现了InitializingBean,那么回调afterPropertiesSet
    • @PostConstruct 注解标注的方法被反射回调
    • 如果BeanDefinition中定义了initMethodName(例如@Bean注解指定初始化方法,xml 声明init-method)

5.Bean的销毁

  1. @PreDestroy 注解修饰的方法被反射回调
  2. 实现了DisposableBean接口,那么回调destroy方法

BeanPostProcessor 中文拙劣的翻译为Bean后置处理器,其中这个后置我没太多理解,此处理器贯穿Bean声明周期,每一个Bean都要经过它的处理

图中没有体现SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference这个在循环依赖中使用到的方法。

这里我们不细聊每一个BeanPostProcessor的方法,着重说一下postProcessAfterInitialization这个方法,此方法在Bean完成初始化后将被回调,其出参是一个Object,如果返回值不为null,那么将由返回的对象替换掉原bean对象。

由此我们可以在postProcessAfterInitialization对原有bean进行AOP增强,并最终被加入Spring BeanFactory中去!

这就是Spring AOP 和IOC的结合点(ps:postProcessBeforeInstantiation,getEarlyBeanReference也由结合的逻辑,postProcessBeforeInstantiation需要我们配置对应的TargetSourceCreator才会生效,getEarlyBeanReference在产生循环依赖的时候会被调用,实现半成品对象也进行AOP增强。循环依赖指路:Spring源码学习笔记8——Spring是如何解决循环依赖的 - Cuzzz - 博客园 (cnblogs.com)

在学习Spring AOP实现原理前,我们先学习下AOP中的几个概念。

1. Advice 通知

定义在连接点做什么,为切面增强提供植入接口。描述Spring AOP围绕方法调而注入的切面行为

2.Pointcut切入点

切点决定Advice通知应该作用在哪个连接点,也就是通过Poincut来定义需要增强的方法集合,这些集合可以按照一定规则来完成,这种情况下,Pointcut意味着标识方法(比如事务切面定义了事务注解方法上生效)切入点是一些列织入逻辑代码的连接点集合

3.Advisor通知器

整合Advice 和 Pointcut,定义应该在哪个关注点使用什么通知进行增强。

4.Joint point连接点

表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point

上面介绍了Spring IOC 和 AOP的结合点和AOP中几个关键的概念,但是具体是如何结合的?下面介绍和AOP功能密切的几个类

注意看AnnotationAwareAspectJAutoProxyCreator是一个BeanPostProcessor,从而在Spring 回调postProcessAfterInitialization对bean进行代理的增强,并且它实现了SmartInstantiationAwareBeanPostProcessor Spring容器创建bean的时候如果出现了循环依赖那么会调用到getEarlyBeanReference,在这个方法里面同样也会进行aop的增强

  • AbstractAutoProxyCreator 实现了SmartInstantiationAwareBeanPostProcessor是一个bean后置处理器,使用 AOP 代理包装每个符合条件的 bean,在调用 bean 本身之前委托给指定的拦截器,AOP代理发生的地方。

  • AbstractAdvisorAutoProxyCreator

    为了每一个Bean找到合适的Advisor并且进行,如果Advisor标注了@Order或者说实现了Ordered接口那么会进行排序。

  • AspectJAwareAdvisorAutoProxyCreator

    AbstractAdvisorAutoProxyCreator子类,它支持AspectJ表达式。

  • AnnotationAwareAspectJAutoProxyCreator

    AspectJAwareAdvisorAutoProxyCreator的子类,会将容器中标注了@AspectJ注解的类解析成Advisor(整合Advice 和 Pointcut,定义应该使用哪个通知器并在哪个关注点使用它)

1.AbstractAutoProxyCreator#postProcessAfterInitialization 在bean初始化后进行代理

其中earlyProxyReferences记录了被代理的早期对象,在下面代码中实现了被代理对象不会再此被代理

2.AbstractAutoProxyCreator#wrapIfNecessary创建代理

其中shouldSkipAspectJAwareAdvisorAutoProxyCreator重写,如果AdvisorAspectJPointcutAdvisor并且切面名称和bean名称相同那么会跳过,这保证了@Aspect标注的类,生成的实例对象不会被代理

2.1.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean找到所有合适的advice 和advisor

  • findCandidateAdvisors方法会找到容器中所以的Advisor类型的bean,AnnotationAwareAspectJAutoProxyCreator进行了重写,它还会把所以标注了@Aspect注解的bean中的增强逻辑封装成Advisor
  • findAdvisorsThatCanApply这个方法内部逻辑基本上就是调用PointcutAdvisor获取类过滤器,方法匹配器进行匹配。
  • sortAdvisors 这里默认是通过@Order注解,或者Ordered接口进行排序,但是AspectJAwareAdvisorAutoProxyCreator进行了重写,因为它需要对同一个标注@Aspect切面里面的前置后置等进行排序

2.2AbstractAutoProxyCreator#createProxy创建代理对象

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
      @Nullable Object[] specificInterceptors, TargetSource targetSource) {

   if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
      AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
   }

   ProxyFactory proxyFactory = new ProxyFactory();
   proxyFactory.copyFrom(this);
   //这里的ProxyTargetClass 来自上面的copyFrom 取决于EnableAspectJAutoProxy注解的proxyTargetClass
    //proxyTargetClass 表示是否使用基于CGLIB子类的代理
   if (!proxyFactory.isProxyTargetClass()) {
       //shouldProxyTargetClass 方法就是去BeanFactory中看当前bean的BeanDefinition中是否存在AutoProxy.PRESERVE_TARGET_CLASS_ATTRIBUTE=trued的attribute,当我们手动注入bean的时候可以使用这个强制让当前bean使用CGLIB增强
      if (shouldProxyTargetClass(beanClass, beanName)) {
         proxyFactory.setProxyTargetClass(true);
      }
      else {
         //获取当前类中非Spring回调(InitializingBean,DisposableBean,Aware)类型的接口,且如果接口的方法大于0,那么会把接口类型加入到proxyFactory中,否则设置ProxyTargetClass(没有接口那么没办法使用JDK动态代理)
         evaluateProxyInterfaces(beanClass, proxyFactory);
      }
   }

   //主要是把上面找到的advise 适配成Advisor。调用的是advisorAdapterRegistry的wrap方法
   Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
   proxyFactory.addAdvisors(advisors);
   //这里的targetSource是SingletonTargetSource
   proxyFactory.setTargetSource(targetSource);
   //留给子类扩展的方法
   customizeProxyFactory(proxyFactory);

   proxyFactory.setFrozen(this.freezeProxy);
   if (advisorsPreFiltered()) {
      proxyFactory.setPreFiltered(true);
   }
    //生成代理对象
   return proxyFactory.getProxy(getProxyClassLoader());
}

可以看到最终使用ProxyFactory.getProxy方法进行动态代理

2.2.1 ProxyFactory是如何创建代理对象的

这里生成的AopProxy 才是负责生成代理对象的,其中spring内置了两种策略——JDK动态代理和CGLIB动态代理。

只有设置了需要代理目标类,或者说没有指定代理的接口,且代理目标类不是接口,不是lambda,不是已经被JDK动态代理后的类,那么才会使用CGLIB进行动态代理。

其中JdkDynamicAopProxy,还实现了InvocationHandler

  • Jdk动态代理

  • 生成代理对象

    Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this),这里的this便是自己。

  • CGLIB动态代理

    • 设置CallBack

      首先new 一个Enhancer设置父类为被代理对象的类型,这里会把讲Aop的逻辑转变为一个DynamicAdvisedInterceptor,equals和hashCode方法也有对应的callBack

      注意这里的MethodInterceptororg.springframework.cglib.proxy.MethodInterceptor,其中的intercept 方法的逻辑和JDK动态代理的invoke类似,都是链式调用。

    • 代理对象生成

      使用Enhancer进行增强

3.AOP代理逻辑的执行

以Jdk动态代理为例子,我们看下增强后对象方法的调用,是怎么进入到Advice中代码逻辑的

3.1 前置逻辑

这里解释了为什么AopContext可以拿到当前代理后的对象!

3.2 方法执行

3.2.1 创建拦截器链

首先有一层cache,避免每次都重复创建

这一步会将Advisor转换成MethodInterceptor,转换的过程使用了AdvisorAdapterRegistry

可以看到它可以将Advisor和MethodInterceptor进行互相转换,其中转换逻辑如下,使用了责任链+适配器模式

以下是三种类型的Adapter和对应转换后的MethodInterceptor

根据这些MethodInterceptor的名称可猜测出它们不同的执行时机!

3.2.1 ReflectiveMethodInvocation#proceed(拦截逻辑+被代理对象方法执行)

ReflectiveMethodInvocation的执行和J2EE中的FilterChain异曲同工之妙。

所谓的Joinpoint执行其实就是反射调用被代理对象的方法

4.AnnotationAwareAspectJAutoProxyCreator(Spring中@Aspect是如何生效)

上面我们讲了其父类AbstractAutoProxyCreator的大体逻辑,AnnotationAwareAspectJAutoProxyCreator会将@Aspect注解类解析成Advisor,下面我们重点看下AnnotationAwareAspectJAutoProxyCreator是怎么将@Aspect注解类解析成Advisor

这里依赖了BeanFactoryAspectJAdvisorsBuilder,它会遍历所有bean,并调用isAspect方法

然后调用ReflectiveAspectJAdvisorFactorygetAdvisors方法将其适配成多个Advisor,会遍历每一个没有标注@Pointcut的方法,然后获取@Around, @Before, @After, @AfterReturning, @AfterThrowing(如果没有那么直接返回)然后获取value中的内容包装成AspectJExpressionPointcut(AspectJ表达式pointcut),然后包装成InstantiationModelAwarePointcutAdvisorImpl在这个类中会把对应注解的方法封装成对应的AbstractAspectJAdvice的子类

如何解析切入点表达式的逻辑,笔者觉得意思不大,没有细看

七丶总结

此篇,和大家一起看了Spring AOP的原理,可以看到Spring AOP实现的原理没有那么神秘,其实就是BeanPostProcessor + 动态代理 + 反射,但是其和Spring IOC结合密切,这正是其”技术壁垒(doge)“,其中也有一些优秀的设计——工厂+代理+责任链+策略+门面设计模式,这些设计模式是真好用呀。

那么事务,@Retryable,@Cacheable,@Async是怎么实现的昵?----它们由各自的Advisor去实现。

这也是Spring AOP的优点之一,后续横切逻辑只需要开发者编写对应的Adivor即可,实现了横切逻辑和业务逻辑的解耦合