Spring 三級緩存機制深度解析
筆者在很早整理過一篇關于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的關鍵,希望對你有幫助。