Spring AOP源码分析一

  • 三哥

内容来自【自学星球】

欢迎大家来了解我的星球,和星主(也就是我)一起学习 Java ,深入 Java 体系中的所有技术。我给自己定的时间是一年,无论结果如何,必定能给星球中的各位带来点东西。

想要了解更多,欢迎访问👉:自学星球

--------------SSM系列源码文章及视频导航--------------

创作不易,望三连支持!

SSM源码解析视频

👉点我

Spring

  1. Spring 中注入 Bean 的各种骚操作做
  2. Spring 中Bean的生命周期及后置处理器使用
  3. Spring 中容器启动分析之refresh方法执行之前
  4. Spring refresh 方法分析之一
  5. Spring refresh 方法之二 invokeBeanFactoryPostProcessors 方法解析
  6. Spring refresh 方法分析之三
  7. Spring refresh 方法之四 finishBeanFactoryInitialization 分析
  8. Spring AOP源码分析一
  9. Spring AOP源码分析二
  10. Spring 事务源码分析

SpringMVC

  1. SpringMVC 启动流程源码分析
  2. SpringMVC 请求流程源码分析

MyBatis

  1. MyBatis 源码分析之 SqlSessionFactory 创建
  2. MyBatis 源码分析之 SqlSession 创建
  3. MyBatis 源码分析之 Mapper 接口代理对象生成及方法执行
  4. MyBatis 源码分析之 Select 语句执行(上)
  5. MyBatis 源码分析之 Select 语句执行(下)
  6. MyBatis 源码分析一二级缓存

---------------------【End】--------------------

经历了漫长的 Spring 启动流程分析之后,我们也是来到了 Spring 中的另一个重点 AOP 相关的内容学习。

回顾 Spring 的启动流程,你们还记得那几个点和 AOP 有着千丝万缕的联系嘛!

  • resolveBeforeInstantiation 方法
  • 第三级缓存
  • @EnableAspectJAutoProxy
  • 标签<aop:aspectj-autoproxy />

上面这四种是我已知的和 AOP 相关的点,下面我就会重点对其进行分析。

按照惯例,先写一个 AOP 案例。

一、AOP 例子

1、编写切面

@Aspect
@Component
public class AspectJTest {

    // 切入点表达式
    @Pointcut("execution(* cn.j3code.studyspring.aop..*.*(..))")
    public void test() {

    }

    // 前置通知
    @Before("test()")
    public void beforeTest() {
        System.out.println("beforeTest。。。");
    }

    // 后置通知
    @After("test()")
    public void afterTest(JoinPoint p) {
        System.out.println("afterTest。。。");
    }

    // 返回通知
    @AfterReturning(pointcut = "test()", returning = "result")
    public void afterReturningTest(JoinPoint p, Object result) {
        System.out.println("afterReturningTest。。。" + result);
    }

    // 异常返回通知
    @AfterThrowing("test()")
    public void afterThrowingTest(JoinPoint p) {
        System.out.println("afterThrowingTest。。。");
    }

    // 环绕通知
    //@Around("test()")
    public Object aroundTest(ProceedingJoinPoint p) {
        System.out.println("before。。。");
        Object proceed = null;
        try {
            proceed = p.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("after。。。");
        return proceed;
    }

}

2、编写配置类

@EnableAspectJAutoProxy // 开启AOP
@Configuration
@ComponentScan("cn.j3code.studyspring.aop")
public class AspectJConfiguration {

}

3、编写目标类

@Service
public class AspectJService {
    public void add() {
        System.out.println("AspectJService====add..........");
    }
}

4、测试

public class AspectJMain {
    public static void main(String[] args) {
        // 读取配置文件启动
        AnnotationConfigApplicationContext annotationApplicationContext =
            new AnnotationConfigApplicationContext(AspectJConfiguration.class);
        AspectJService bean = annotationApplicationContext.getBean(AspectJService.class);
        bean.add();
    }
}

大家可以关注一下配置文件上的一个注解 @EnableAspectJAutoProxy ,它非常重要,如果没有它我们的 AOP 功能将不会起效果。如果大伙是 XML 配置文件的形式启动 Spring 容器,那么则需要在配置文件中配置下面这个标签:

<aop:aspectj-autoproxy/>

ok,例子给大家讲完了,那再来看看 Spring AOP 中的一些术语。

二、AOP 术语

在 Spring AOP 源码中有很多相关的类基本上都是和这些术语有关,所以先了解这个,有助于理解源码。

1、通知(Advice)

通知也可以说是增强,其可分为:前置通知、后置通知、异常通知、最终通知与环绕通知。

2、连接点(JoinPoint)

连接点就是被代理类中的所有方法。

3、切入点(Pointcut)

切入点是被增强过的方法,注意这里它和连接点的区别,有些方法可能并没有被增强,而是直接返回,这些方法只是连接点,并不是切入点。切入点一定是连接点,连接点不一定切入点。

4、切面(Aspect)

切面是通知和切入点的结合,就是上面我们编写的 AspectJTest 类。

5、引入(introduction)

向现有的类添加切面中定义的通知方法,即将切面用到目标类中。

6、目标(target)

被代理的对象,如上面例子中的 AspectJService 类。

7、代理(proxy)

代理出来的对象。

8、织入(weaving)

把切面应用到目标对象来创建新的代理对象的过程。

三、resolveBeforeInstantiation 返回代理的机制

在 Bean 的创建流程中 resolveBeforeInstantiation 方法的作用时机如下图:

在这里插入图片描述

由图可知,该方法的返回代理时机是不参与 Bean 的生命周期流程的,即返回的代理对象没有执行 Bean 的生命周期流程。

方法源码:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation

protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
    Object bean = null;
    if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
        // Make sure bean class is actually resolved at this point.
        // mbd不是合成的,并且BeanFactory中存在InstantiationAwareBeanPostProcessor
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            // 解析beanName对应的Bean实例的类型
            Class<?> targetType = determineTargetType(beanName, mbd);
            if (targetType != null) {
                // 实例化前的后置处理器应用(处理InstantiationAwareBeanPostProcessor)
                bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
                if (bean != null) {
                    // 如果返回的bean不为空,会跳过Spring默认的实例化过程
                    // 所以只能在这里调用BeanPostProcessor实现类的PostProcessorsAfterInitialization方法
                    bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
                }
            }
        }
        // 如果bean不为空,则将beforeInstantiationResolved赋值为true,代表在实例化之前已经解析
        mbd.beforeInstantiationResolved = (bean != null);
    }
    return bean;
}

这里我们的重点就是两个 BeanPostProcessors 应用方法,既 applyBeanPostProcessorsBeforeInstantiation 与 applyBeanPostProcessorsAfterInitialization 分别干了些什么事情。

先看 applyBeanPostProcessorsBeforeInstantiation 方法源码。

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation

protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
    // 遍历当前BeanFactory中的BeanPostProcessor
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        // 应用InstantiationAwareBeanPostProcessor后置处理器,允许PostProcessorBeforeInstantiation方法返回bean对象的代理
        if (bp instanceof InstantiationAwareBeanPostProcessor) {
            InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
            // 执行postProcessBeforeInstantiation方法,在Bean实例化前操作,
            // 该方法可以返回一个构造完成的Bean实例,从而不会继续执行创建Bean实例的"正规流程",达到"短路"效果
            Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
            if (result != null) {
                // 如果result不为null,也就是有后置处理器返回了bean实例对象,则会跳过Spring默认的实例化过程。
                return result;
            }
        }
    }
    return null;
}

该方法的执行流程我们是不是已经看过很多这种的了,先获取容器中的所有后置处理器,然后挨个遍历并执行对应的处理器方法。

所以这次我们的重点放到 postProcessBeforeInstantiation 方法干了些啥事。

那么问题来了,这个方法我们是进入那个类呢!这里我就直接说了,这个:AbstractAutoProxyCreator。

因为我们是分析 AOP 所以看名字我们也知道是进入这个类。

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessBeforeInstantiation

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
    // 获取BeanClass的缓存key
    Object cacheKey = getCacheKey(beanClass, beanName);

    if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
        // advisedBeans保存了所有已经做过动态代理的Bean
        // 如果被解析过则直接返回
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
        }
        // 判断当前bean是否是基础类型:是否实现了Advice,Pointcut,Advisor,AopInfrastructureBean这些接口或是否是切面(@Aspect注解)
        // 判断是不是应该跳过 (AOP解析直接解析出我们的切面信息,而事务在这里是不会解析的)
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }

    // Create proxy here if we have a custom TargetSource.
    // Suppresses unnecessary default instantiation of the target bean:
    // The TargetSource will handle target instances in a custom fashion.
    // 获取用户自定义的targetSource, 如果存在则直接在对象实例化之前进行代理创建,
    // 避免了目标对象不必要的实例化
    TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
    // 如果有自定义targetSource就要这里创建代理对象
    // 这样做的好处是被代理的对象可以动态改变,而不是值针对一个target对象(可以对对象池中对象进行代理,可以每次创建代理都创建新对象
    if (targetSource != null) {
        if (StringUtils.hasLength(beanName)) {
            this.targetSourcedBeans.add(beanName);
        }
        // 获取Advisors, 这个是交给子类实现的
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
        // 创建代理对象
        Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
        this.proxyTypes.put(cacheKey, proxy.getClass());
        // 返回代理的对象
        return proxy;
    }

    return null;
}

这个方法主要分两个部分:

  • 第一部分:容器中没有 TargetSource 类型 Bean,方法返回 null 。
  • 第二部分:容器中有 TargetSource 类型 Bean,方法返回 TargetSource 的代理。

先来说说第一部分,如果第一部分成立,那么就会走 if 分支。根据源码意思,如果需要代理的类已经存在了那就不用在进行代理了直接返回 null 。如果不成立,那么继续判断需要代理的类是不是切面类或者需要跳过,当然结果还是返回 null 。

这里有个重点就是是否需要跳过也即:shouldSkip(beanClass, beanName) 方法。

这里我先说该方法重点干了些啥,具体的源码分析留到后面一起分析。就是 shouldSkip 方法会提前将我们的切面类进行解析分析出切面中的通知方法,后续就可以直接从缓存中获得切面类中的各种通知方法

再来说说第二部分,如果第二部分成立,那么毫无疑问程序中存在 TargetSource 类型的 Bean 需要 Spring 进行相关代理,那么 TargetSource 代理又是个什么鬼,这里我给大家找来了几个资料,如下

资料一:https://www.icode9.com/content-4-1380416.html

总结 resolveBeforeInstantiation 方法的几个重要点

1、如果程序中不存在 AOP 功能,那么 resolveBeforeInstantiation 就相当于空方法。

2、如果存在 AOP 功能,那么如果程序中如果没有 TargetSource 类型的相关 Bean ,该方法则只会做一件事情就是 shouldSkip 方法的提前解析切面类。

3、如果程序中存在 TargetSource 类型 Bean ,那么程序会对该类型进行相关代理,返回代理对象提前结束 Bean 的创建流程,也即短路。

四、三级缓存返回代理的机制

这里我说的是三级缓存返回代理而不是循环依赖,这个要搞清楚。

不过,如果要分析三级缓存的话,还真要把循环依赖这个东西抛出来说一说,不然很难知道为啥出现一个三级缓存。

4.1 循环依赖

循环依赖的概念在网上已经有很多了,这里就不详细说概念了,那何为循环依赖,简单讲就是 A 中有个属性 B ,B 中有个属性 A ,在实例化 A 和 B 的时候出现的循环引用现象称为循环依赖。

那这个能解决吗?

  • set 方式能解决
  • 构造器方式不能解决
  • 不全是构造器方式也能解决

怎么理解上面的三种情况呢!

第一种:

在这里插入图片描述

第二种:

在这里插入图片描述

第三种:

在这里插入图片描述

前面两种好理解,那第三种是怎么理解呢!

第三种的理解方式为,如果最先创建的 Bean 中属性注入的方式非构造器方式注入则能解决循环依赖,反之则不能,剧透原因会在源码中找到答案。

4.2 三级缓存解决循环依赖

我们按照 4.1 节中的第一种情况分析循环依赖问题。

先来看看 A 和 B 对象的创建的整体流程:

在这里插入图片描述

我觉得上面的图已经画的非常清楚了,其中有两个方法尤为重要

  • getSingleton(beanName):提前从缓存中获取对象
  • addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)):将为创建完的对象放到缓存中

当 A 进行创建的时候,会将半成品放入到缓存中,然后再执行 A 的属性赋值。当发现 A 中有个 B 类型的属性需要赋值时,则会反过来创建 B ,并将半成品 B 放入到缓存中,然后执行 B 的属性赋值。而 B 中又刚好有个 A 属性需要赋值,则正好可以从缓存中获取 A 的半成品进行赋值,最终 B 对象完成属性赋值,创建完成,接着 A 对象的 B 属性也赋值成功,A 对象也创建成功。

现在来解答为啥构造器方式注入不能解决循环依赖:

答:因为执行调用构造器创建对象在将对象放入缓存的流程之前,所以当调用构造器时发现需要自动装配而去缓存中查找时,会找不到对应的值,所以就不能解决此循环依赖。(同理上面循环依赖的第三种)

那最后我们再聊聊为什么三级缓存和 AOP 代理有关系。

来到从缓存中获取对象的方法

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从单例对象缓存中获取beanName对应的单例对象
    Object singletonObject = this.singletonObjects.get(beanName);
    // 如果单例对象缓存中没有,并且该beanName对应的单例bean正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 尝试给一级缓存对象加锁,因为接下来就要对缓存对象操作了
        synchronized (this.singletonObjects) {
            // 从早期单例对象缓存中获取单例对象(之所称成为早期单例对象,是因为earlySingletonObjects里
            // 的对象的都是通过提前曝光的ObjectFactory创建出来的,还未进行属性填充等操作)
            // 尝试从二级缓存earlySingletonObjects这个存储还没进行属性添加操作的Bean实例缓存中获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // 如果在早期单例对象缓存中也没有,并且允许创建早期单例对象引用
            // 如果还没有获取到并且第二个参数为true,为true则表示bean允许被循环引用
            if (singletonObject == null && allowEarlyReference) {
                // 从单例工厂缓存中获取beanName的单例工厂
                // 从三级缓存singletonFactories这个ObjectFactory实例的缓存里尝试获取创建此Bean的单例工厂实例
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                // 如果获取到工厂实例
                if (singletonFactory != null) {
                    // 如果存在单例对象工厂,则通过工厂创建一个单例对象
                    singletonObject = singletonFactory.getObject();
                    // 将通过单例对象工厂创建的单例对象,放到早期单例对象缓存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放到earlySingletonObjects缓存了,
                    // 因此,后续获取beanName的单例对象,可以通过earlySingletonObjects缓存拿到,不需要在用到该单例工厂
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    // 返回单例对象
    return singletonObject;
}

这段代码基本上就是从三个 Map 中获取对象,而真正返回对象的确实下面这行代码

singletonFactory.getObject()

而这个方法会则会调用存入时传入的匿名内部来中的方法,也既下面代码

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                // 调用 SmartInstantiationAwareBeanPostProcessor 类型的 getEarlyBeanReference 方法获取对象
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

这个方法就有意思了,如果程序中没有 SmartInstantiationAwareBeanPostProcessor 类型的后置处理器就相当于下面这个代码

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    return exposedObject;
}

所以 SmartInstantiationAwareBeanPostProcessor 类型是啥?

我们看一下这个类的继承结构图

在这里插入图片描述

不难发现,是和 AOP 相关。

而且我们进入到 ibp.getEarlyBeanReference 方法中也可以发现只有下面这个方法有内容,其他都是不作处理

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference

public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    if (!this.earlyProxyReferences.contains(cacheKey)) {
        this.earlyProxyReferences.add(cacheKey);
    }
    // 代理 Bean 的流程(后面会分析这个方法)
    return wrapIfNecessary(bean, beanName, cacheKey);
}

所以,通过以上一系列的推导,当 A 对象创建的时候注入 B 属性类型时,如果 B 类型是被 AOP 切面的切入点表达式切到的,那么自动装配的就不是 B 的普通类型了,而是 B 的代理类型。

那我们趁热打铁,再来说说面试中常问的,为啥 Spring 中要有三级缓存解决循环依赖而不是二级缓存

来,我们结合上面的内容答:从第三级缓存中拿到的对象有两种,代理对象或普通对象。如果只有二级缓存,那么在自动装配的时候,从二级缓存中拿到的对象到底是普通对象呢还是代理对象呢!如果二级缓存都存普通对象显然不合适,因为自动装配的有可能是个代理对象;那我二级缓存都存放代理对象总可以了吧!这也不行,因为自动装配的对象有可能不需要代理。所以面对这种情况,第三级缓存横空出世!需要自动装配的对象,必须先从第三级缓存中拿,在第三级缓存中完成普通对象或代理对象的处理。

好了,今天的内容到这里就结束了,我是 【J3】关注我,我们下期见


  • 由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。

  • 如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。

  • 感谢您的阅读,十分欢迎并感谢您的关注。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J3code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值