Spring 三级缓存机制深度解析

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

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

一、给出本文的代码示例

因为三级缓存的设计是为了解决代理对象多例创建问题,所以在讲解三级缓存之前,我们需要给出一段关于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

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

1.三级缓存的定义

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

  • singletonObjects :即单例缓存Map,其内部存储的都是以bean的名称为key,已经完全创建的bean作为value的Map。
  • singletonFactories :该缓存都bean的工厂方法,可能有些读者不太理解,我们就以上文的AService为例,在Spring进行每一个bean初始化时,都会为这个bean封装一个工厂方法用于bean的创建,这一点笔者也会在后续的代码中提及,这里我们只需要知道这个缓存是以bean的名称作为key,创建该bean的工厂方法为value的Map即可。
  • 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);

2.Spring 如何解决循环依赖问题

我们以两个互相依赖的bean来探讨Spring在进行加载时如何完成循环依赖的bean的初始化:

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

    @Autowired
    private BService bService;  

}


@Service("bService")
public class BService {
    @Autowired
    private AService aService;

}

假设我们现在先创造aService  进行各项初始化之前,容器会先将其加载至三级缓存也就是工厂缓存中:

随后在属性填充阶段发现要填充bService,发现容器中各级缓存都没有bService,于是到容器中尝试加载bService,bService初始化过程与aService同理先加载到3级缓存中:

与此同时bService发现需要加载aService,此时在三级缓存中看到aService将其生成半成品bean放至二级缓存中并注入到bService中,此时bService完成加载称为一个完成的对象,并直接存放至一级缓存中:

最后回到aService完成bService的注入,加载到一级缓存中,由此解决循环依赖的加载问题:

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

我们还是以创建Aservice为例注入bService同时进行增强为例讲解一下这个过程,当Spring容器进行Aservice创建时代码就走到AbstractAutowireCapableBeanFactory的doGetBean方法,对应步骤为:

(1) 因为是初次创建,所以代码调用addSingletonFactory方法为Aservice创建工厂方法并将其存入singletonFactories这个三级工厂缓存中。

(2) 就会调用populateBean进行属性填充完成相关依赖注入,在此期间bService会将工厂缓存中的aService基于各种SmartInstantiationAwareBeanPostProcessor 生成增强类完成注入:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
  //拿到aService的一级缓存
  Object exposedObject = bean;
  //基于各种SmartInstantiationAwareBeanPostProcessor 生成增强类
  if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
   for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
    exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
   }
  }
  return exposedObject;
 }

(3) 在者循环期间就会找到一个AnnotationAwareAspectJAutoProxyCreator的动态代理bean,其内部会通过策略模式找到AnnotationAwareAspectJAutoProxyCreator扩展点内部会判断是否需要代理,然后通过策略模式走到CGLIB进行增强,自此aservice就被代理了。

@Override
 public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   //查看是否因为注入等原因创建了代理,如果创建了则直接返回当前bean,后续逻辑从容器中获取代理对象
   if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    //判断是否需要代理,如果需要则通过策略定位到加强类完成加强
    return wrapIfNecessary(bean, beanName, cacheKey);
   }
  }
  return bean;
 }

(4) 完成这步骤后bService就完整了,然后回到aService发现二级缓存已经有了代理类,直接拿着这个代理返回存入一级缓存中:

if (earlySingletonExposure) {
  //发现二级缓存中有代理类,直接拿着这个代理类存入一级缓存中   
   Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {
    if (exposedObject == bean) {
     exposedObject = earlySingletonReference;
    }

}

4.为什么需要三级缓存

看过很多网上的解答,都是认为通过三级缓存解决循环依赖中出现的各种错误,笔者认为针对循环依赖问题,通过二级乃至一级缓存都可以解决问题。这里笔者假设是二级,aService也可以按照如下顺序完成:

  • 将自己放入一级缓存。
  • 查找依赖的bService,创建其一级缓存,然后创建成代理对象存入一级。
  • 注入bService。

实际上Spring引入三级缓存的原因并不是解决这些所谓的"问题",而是在设计之初引入AOP时为保证所有的代理生成工作应该尽量在bean初始化阶段的后置初始化postProcessAfterInitialization等扩展点完成,由此引入三级缓存,通过稍稍打破原则即通过三层缓存提前为需要代理对象的实例的bean生成代理对象完成依赖注入。

小结

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

责任编辑:赵宁宁 来源: 写代码的SharkChili
相关推荐

2023-12-12 17:44:13

三级缓存Bean

2024-03-04 08:47:17

Spring框架AOP

2010-11-25 09:37:14

MySQL查询缓存机制

2022-05-08 19:23:28

Spring循环依赖

2022-12-02 12:01:30

Spring缓存生命周期

2022-03-01 18:03:06

Spring缓存循环依赖

2023-02-26 11:15:42

缓存循环依赖

2020-02-06 13:40:35

编程缓存优化

2024-03-18 00:00:00

SpringBean设计

2024-04-12 07:51:05

SpringBean初始化

2021-01-29 14:14:47

动态代理缓存

2021-09-04 07:29:57

Android

2011-08-02 18:07:03

iPhone 内省 Cocoa

2024-10-12 12:55:26

2019-08-08 15:47:03

HTTP缓存CDN

2010-11-10 09:13:37

综合布线综合布线改造

2009-09-24 11:16:22

CCNA和CCNP

2020-02-23 15:45:02

大数据疫情运营

2024-04-15 08:17:21

Spring依赖注入循环依赖

2011-12-22 17:42:16

点赞
收藏

51CTO技术栈公众号