0
点赞
收藏
分享

微信扫一扫

Spring AOP源码分析一

_karen 2022-10-30 阅读 187


经历了漫长的 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 方法的作用时机如下图:

Spring AOP源码分析一_jvm

由图可知,该方法的返回代理时机是不参与 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 方式能解决
  • 构造器方式不能解决
  • 不全是构造器方式也能解决

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

第一种:

Spring AOP源码分析一_jvm_02

第二种:

Spring AOP源码分析一_spring_03

第三种:

Spring AOP源码分析一_AOP_04

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

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

4.2 三级缓存解决循环依赖

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

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

Spring AOP源码分析一_spring_05

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

  • 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 类型是啥?

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

Spring AOP源码分析一_缓存_06

不难发现,是和 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 中要有三级缓存解决循环依赖而不是二级缓存​​?

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

举报

相关推荐

0 条评论