暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

聊一聊Spring为什么需要三级缓存

34

写在文章开头

笔者在很早整理过一篇关于AOP
的源码的文章,阅读起来晦涩难懂,在复盘时就有了想重构的想法,所以就借着这一话题重新聊一聊Spring中的三级缓存。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注  “加群”  即可和笔者和笔者的朋友们进行深入交流。

给出本文的代码示例

因为三级缓存的设计是为了解决代理对象多例创建问题,所以在讲解三级缓存之前,我们需要给出一段关于AOP的示例代码以便有着一个直观的切入点来理解这个问题,我们首先给出代理类AService
 :


@Service("aService")
@Slf4j
public class AService  {

    @Autowired
    private BService bService;

    public void hello(){
        log.info("hello world");
    }

}


然后再给出代理对象的代码示例:

@Aspect
@Slf4j
@Component
public class AopProxy {

    @Before("execution(* com.sharkChili.service.AService.hello())")
    public void proxyFunction() {
        log.info("aService被代理了");
    }
}

如此一来,我们的服务被调用后,代理对象就会拦截hello
方法,在其执行前打印aService被代理了

2024-05-14 23:35:12.456  INFO 6652 --- [           main] com.sharkChili.aspect.AopProxy           : aService被代理了
2024-05-14 23:35:12.462  INFO 6652 --- [           main] com.sharkChili.service.AService          : hello world

基于源码详解三级缓存设计

三级缓存的定义

我们可以在DefaultSingletonBeanRegistry
这个类的定义中看到三级缓存对象,它们分别是:

  1. singletonObjects
    :即单例缓存Map
    ,其内部存储的都是以bean
    的名称为key
    ,已经完全创建的bean
    作为value
    Map
  2. singletonFactories
    :该缓存都bean
    的工厂方法,可能有些读者不太理解,我们就以上文的AService
    为例,在Spring
    进行每一个bean
    初始化时,都会为这个bean封装一个工厂方法用于bean
    的创建,这一点笔者也会在后续的代码中提及,这里我们只需要知道这个缓存是以bean的名称作为key,创建该bean
    的工厂方法为value
    Map
    即可。
  3. earlySingletonObjects
    :其内部缓存的都是未完全完成创建的bean
    ,就比如某个Service
    中的Mapper
    还未被注入,那么这个Service
    就会被缓存到这个Map
    中。

对应的我们给出DefaultSingletonBeanRegistry
中对于这三级缓存定义:

 /** Cache of singleton objects: bean name to bean instance. */
 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

 /** Cache of singleton factories: bean name to ObjectFactory. */
 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

 /** Cache of early singleton objects: bean name to bean instance. */
 private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

基于源码详解三级缓存的作用

我们以创建Aservice
为例,当Spring
容器进行Aservice
创建时代码就走到AbstractAutowireCapableBeanFactory
doGetBean
方法,因为是初次创建,所以代码调用addSingletonFactory
方法为Aservice
创建工厂方法并将其存入singletonFactories
这个缓存中。然后就会调用populateBean
进行属性填充这期间通过二级缓存完成依赖注入,然后来到initializeBean
,这里就是三级缓存使用的关键点了,该方法会循环遍历各种BeanPostProcessorsAfterInitialization
postProcessBeforeInitialization
方法,最终找到一个AnnotationAwareAspectJAutoProxyCreator
的动态代理bean
,其内部会通过策略模式找到何时的动态代理类加强AService

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
   throws BeanCreationException 
{

  //......
   
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
   //......
   //创建当前bean的工厂方法并存入singletonFactories缓存中
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  //......
  Object exposedObject = bean;
  try {
   //进行属性填充
   populateBean(beanName, mbd, instanceWrapper);
   //初始化bean找到对应的扩展点完成动态代理
   exposedObject = initializeBean(beanName, exposedObject, mbd);
  }
  catch (Throwable ex) {
   //......
   }
  }

  //.....

  return exposedObject;
 }

这里我们也贴出initializeBean
内部关于applyBeanPostProcessorsAfterInitialization
的方法,可以看出它会遍历容器中的BeanPostProcessors
从而调用applyBeanPostProcessorsAfterInitialization

@Override
 public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
   throws BeansException 
{

  Object result = existingBean;
  for (BeanPostProcessor processor : getBeanPostProcessors()) {
  //Aservice会找到AnnotationAwareAspectJAutoProxyCreator完成类的加强
   Object current = processor.postProcessAfterInitialization(result, beanName);
   if (current == null) {
    return result;
   }
   result = current;
  }
  return result;
 }

AnnotationAwareAspectJAutoProxyCreator
扩展点内部会判断是否需要代理,然后通过策略模式走到CGLIB
进行增强,自此aservice
就被代理了。

@Override
 public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    //判断是否需要代理,如果需要则通过策略定位到加强类完成加强
    return wrapIfNecessary(bean, beanName, cacheKey);
   }
  }
  return bean;
 }

为什么需要三级缓存

通过上述的流程我们了解到的Spring
动态代理的全过程,我们不妨假设一下这种场景,Aservice
被两个加强类代理,假设执行到动态代理时这个类还未创建完全,如果少了二级缓存earlySingletonObjects
这就意味着动态代理拦截的bean
都需要从一级缓存singletonFactories
获取,而singletonFactories
缓存的是bean的工厂方法,这就使得一个bean
会出现多例的情况:

对应的我们给出getSingleton
的方法,可以看到Spring
就是通过三级缓存解决动态代理时频繁调用工厂方法导致多例bean
的问题:

@Nullable
 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  //从singletonObjects查看是否有创建完全的bean
  Object singletonObject = this.singletonObjects.get(beanName);
  //若没有则到earlySingletonObjects查看有没有已创建但未完全创建的bean
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
   singletonObject = this.earlySingletonObjects.get(beanName);
   //若earlySingletonObjects还是没有则通过DCL通过singletonFactories创建一个bean并返回
   if (singletonObject == null && allowEarlyReference) {
    synchronized (this.singletonObjects) {
     // Consistent creation of early reference within full singleton lock
     singletonObject = this.singletonObjects.get(beanName);
     if (singletonObject == null) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null) {
       ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
       if (singletonFactory != null) {
        //创建单例bean将其缓存至earlySingletonObjects中,并将其这个bean的工厂方法移除
        singletonObject = singletonFactory.getObject();
        this.earlySingletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
       }
      }
     }
    }
   }
  }
  return singletonObject;
 }

小结

本文基于一段代码示例讲解了三级缓存的Spring框架中的使用,通过对于AOP的增强时期了解到三级缓存是保证单例bean的关键,希望对你有帮助。

我是 sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注  “加群”  即可和笔者和笔者的朋友们进行深入交流。

参考

京东一面:Spring 为何需要三级缓存解决循环依赖,而不是二级缓存?我懵了。。:https://mp.weixin.qq.com/s/DoewrSzmsJtTjy66ZfLolA


文章转载自写代码的SharkChili,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论