Spring循環依賴源碼詳解,改用兩級緩存并實驗
背景
最近一直在研究Spring的循環依賴,發現好像兩級緩存也能解決循環依賴。
關于為何使用三級緩存,大致有兩個原因
- 對于AOP的類型,保證Bean生命周期的順序
對于有AOP代理增強的類型,如果沒有循環依賴,那么AOP的增強邏輯的執行點在:
無循環依賴:
Container->>Bean: 1. 實例化(constructor)Container->>Bean: 2. 屬性注入(populate)Container->>Processor: 3. 調用postProcessAfterInitialization()Processor->>Processor: 4. 創建代理(wrapIfNecessary)Processor-->>Container: 5. 返回代理對象
在第4步,也就是初始化后置處理器postProcessAfterInitialization
實際代理包裝在BeanPostProcessor子類AbstractAutoProxyCreator類中:
public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean); // 標記為提前代理return wrapIfNecessary(bean, beanName, cacheKey); // 創建代理
}
有循環依賴
Container->>Bean: 1. 實例化(半成品)Container->>EarlyCache: 2. 存入singletonFactoriesContainer->>Bean: 3. 屬性注入(觸發循環依賴)Container->>EarlyCache: 4. 獲取早期引用 → getEarlyBeanReference()EarlyCache->>Processor: 5. 調用getEarlyBeanReference()Processor->>Processor: 6. 創建代理(提前)Processor-->>Container: 7. 返回代理對象Container->>Bean: 8. 繼續屬性注入和初始化Container->>Processor: 9. postProcessAfterInitialization() Processor-->>Container: 10. 返回同一個代理(已存在則不重復創建)
這里是在循環依賴注入的過程中發生的,提前了
其實在哪里進行代理并無實際影響,因為不會影響類實例的成員
2、第二個原因
是在實例化后依賴注入之前,會把這個ObjectFactory的對象放到三級緩存,延遲創建代理實例,后續有循環依賴,回到三級緩存拿到這個,并調用ObjectFactory.getObject方法進行真正的創建,多次調用會產生多個實例,這里可以及時創建實例,不必等到延遲加載,就解決了
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
改用兩級緩存
(針對單例循環setter場景,修改spring源碼,三級緩存改為兩級緩存)
/*** 修改:20250819 11:57 直接加入二級緩存 不用三級緩存 看一下能不能解決循環依賴* @param beanName* @param singletonFactory*/protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {//this.singletonFactories.put(beanName, singletonFactory); // 加入工廠 暴露給外部this.earlySingletonObjects.put(beanName,singletonFactory.getObject());// 確保二級緩存不會存在相同的beanthis.registeredSingletons.add(beanName);}}}
這里直接把三級緩存注釋,在實例化完成后直接生成代理對象
創建測試類
@Aspect // 切面類
@Component
public class LogAspect {// 切入點表達式:匹配所有 MyServiceImpl 下的方法@Pointcut("execution(* com.jdkProxy.MyServiceImpl.*(..))")public void userServiceMethods() {// 方法體必須為空,不能寫任何邏輯!}// 前置通知:方法執行前@Before("userServiceMethods()")public void beforeMethod(JoinPoint joinPoint) {System.out.println("📌 Before method: " + joinPoint.getSignature().getName());}// 后置通知:方法正常返回后@AfterReturning(pointcut = "userServiceMethods()", returning = "result")public void afterReturning(JoinPoint joinPoint, Object result) {System.out.println("? Method returned: " + joinPoint.getSignature().getName());}// 異常通知:方法拋出異常后@AfterThrowing(pointcut = "userServiceMethods()", throwing = "ex")public void afterThrowing(JoinPoint joinPoint, Exception ex) {System.out.println("💥 Exception in method: " + joinPoint.getSignature().getName() + ", ex: " + ex);}// 環繞通知:可以控制整個方法執行@Around("userServiceMethods()")public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("🔄 Around before: " + pjp.getSignature().getName());Object result = pjp.proceed(); // 執行目標方法System.out.println("🔄 Around after: " + pjp.getSignature().getName());return result;}
}
@Component
public class MyServiceImpl implements MyService{@AutowiredMyServiceImpl2 myServiceImpl2;public MyServiceImpl2 getMyServiceImpl2() {return myServiceImpl2;}public void setMyServiceImpl2(MyServiceImpl2 myServiceImpl2) {this.myServiceImpl2 = myServiceImpl2;}public void eat(){System.out.println("吃飯服務");}@Overridepublic void mainMethod() {eat();}
}
@Component
public class MyServiceImpl2 {@AutowiredMyServiceImpl myService;public MyServiceImpl getMyService() {return myService;}public void setMyService(MyServiceImpl myService) {this.myService = myService;}
}
@Component
public class MyServiceImpl3 {@AutowiredMyServiceImpl myService;public MyServiceImpl getMyService() {return myService;}public void setMyService(MyServiceImpl myService) {this.myService = myService;}
}
MyServiceImpl ->myServiceImpl2
MyServiceImpl2 -> MyServiceImpl
MyServiceImpl3 ->MyServiceImpl
啟動容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();context.scan("com"); // 掃描包中的注解 進行BeanDefinintion 注冊context.refresh();MyServiceImpl m1 = context.getBean(MyServiceImpl.class);System.out.println("m1->m2:"+m1.getMyServiceImpl2());MyServiceImpl2 m2 = context.getBean(MyServiceImpl2.class);MyServiceImpl3 m3 = context.getBean(MyServiceImpl3.class);System.out.println("m1:"+m1);System.out.println("m2->m1:"+m2.getMyService());m2.getMyService().eat();System.out.println("m3->m1:"+m3.getMyService());m3.getMyService().eat();
輸出結果:
m1:com.jdkProxy.MyServiceImpl@29c2c826
m2:com.jdkProxy.MyServiceImpl2@253b380a
m3:com.jdkProxy.MyServiceImpl3@6818d900
------------------------------------------
🔄 Around before: getMyServiceImpl2
📌 Before method: getMyServiceImpl2
? Method returned: getMyServiceImpl2
🔄 Around after: getMyServiceImpl2
m1->m2:com.jdkProxy.MyServiceImpl2@253b380a
m2->m1:com.jdkProxy.MyServiceImpl@29c2c826
m3->m1:com.jdkProxy.MyServiceImpl@29c2c826
------------------------------------------
🔄 Around before: eat
📌 Before method: eat
吃飯服務
? Method returned: eat
🔄 Around after: eat
------------------------------------------
🔄 Around before: eat
📌 Before method: eat
吃飯服務
? Method returned: eat
🔄 Around after: eat
🔄 Around before: eat
📌 Before method: eat
吃飯服務
? Method returned: eat
🔄 Around after: eat
🔄 Around before: mainMethod
📌 Before method: mainMethod
吃飯服務
? Method returned: mainMethod
🔄 Around after: mainMethod