spring解決循環依賴爲何要用三級緩存?

關注「蘇三說技術」,回覆:開發手冊、時間管理 有驚喜。javascript

       

也許有些朋友對spring的循環依賴問題並不瞭解,讓咱們先一塊兒看看這個例子。
java

@Servicepublic class AService {
private BService bService;
public AService(BService bService) { this.bService = bService; }
public void doA() { System.out.println("call doA"); }}


@Servicepublic class BService {
private AService aService;
public BService(AService aService) { this.aService = aService; }
public void doB() { System.out.println("call doB"); }}


@RequestMapping("/test")@RestControllerpublic class TestController {
@Autowired private AService aService;
@RequestMapping("/doSameThing") public String doSameThing() { aService.doA(); return "success"; }}


@SpringBootApplicationpublic class Application {
/** * 程序入口 * @param args 程序輸入參數 */ public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args); }}


咱們在運行Application類的main方法啓動服務時,報了以下異常:web

Requested bean is currently in creation: Is there an unresolvable circular reference?

這裏提示得很明顯,出現了循環依賴。
spring


什麼是循環依賴?
typescript


循環依賴是實例a依賴於實例b,實例b又依賴於實例a。緩存


或者實例a依賴於實例b,實例b依賴於實例c,實例c又依賴於實例a。微信

像這種多個實例之間的相互依賴關係構成一個環形,就是循環依賴。
app


爲何會造成循環依賴?ide

上面的例子中AService實例化時會調用構造方法 public AService(BService bService),該構造方法依賴於BService的實例。此時BService尚未實例化,須要調用構造方法public BService(AService aService)才能完成實例化,該構造方法巧合又須要AService的實例做爲參數。因爲AServiceBService都沒有提早實例化,在實例化過程當中又相互依賴對方的實例做爲參數,這樣構成了一個死循環,因此最終都沒法再實例化了。函數


spring要如何解決循環依賴?

只須要將上面的例子稍微調整一下,不用構造函數注入,直接使用Autowired注入。

@Servicepublic class AService {
@Autowired private BService bService;
public AService() { }
public void doA() { System.out.println("call doA"); }}


@Servicepublic class BService {
@Autowired private AService aService;
public BService() { }
public void doB() { System.out.println("call doB"); }}

咱們看到能夠正常啓動了,說明循環依賴被本身解決了


spring爲何能循環依賴?

調用applicationContext.getBean(xx)方法,最終會調到AbstractBeanFactory類的doGetBean方法。因爲該方法很長,我把部分不相干的代碼省略掉了。

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean;
Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { 省略........ bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else {       省略........
if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } else { String scopeName = mbd.getScope(); final Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { Object scopedInstance = scope.get(beanName, () -> { beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Scope '" + scopeName + "' is not active for the current thread; consider " + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", ex); } } } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } 省略........ return (T) bean; }

咱們能夠看到,該方法一進來會調用getSingleton方法從緩存獲取實例,若是獲取不到。會判斷做用域是否爲:單例,多列 或者 都不是,不一樣的做用域建立實例的規則不同。接下來,咱們重點看一下getSingleton方法

 public Object getSingleton(String beanName) { return getSingleton(beanName, true); }


 protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }

咱們發現有三個Map集合:

 /** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

singletonObjects對應一級緩存,earlySingletonObjects對應二級緩存,singletonFactories對應三級緩存。


上面getSingleton方法的邏輯是:

  1. 先從singletonObjects(一級緩存)中獲取實例,若是能夠獲取到則直接返回singletonObject實例。

  2. 若是從singletonObjects(一級緩存)中獲取不對實例,再從earlySingletonObjects(二級緩存)中獲取實例,若是能夠獲取到則直接返回singletonObject實例。

  3. 若是從earlySingletonObjects(二級緩存)中獲取不對實例,則從singletonFactories(三級緩存)中獲取singletonFactory,若是獲取到則調用getObject方法建立實例,把建立好的實例放到earlySingletonObjects(二級緩存)中,而且從singletonFactories(三級緩存)刪除singletonFactory實例,而後返回singletonObject實例。

  4. 若是從singletonObjectsearlySingletonObjectssingletonFactories中都獲取不到實例,則singletonObject對象爲空。


獲取實例須要調用applicationContext.getBean("xxx")方法,第一次調用getBean方法,代碼走到getSingleton方法時返回的singletonObject對象是空的。而後接着往下執行,默認狀況下bean的做用域是單例的,接下來咱們重點看看這段代碼:

createBean方法會調用doCreateBean方法,該方法一樣比較長,咱們把不相干的代碼省略掉。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;    省略......     if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); }    final Object bean = instanceWrapper.getWrappedInstance();    省略........
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));    if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) {      省略 ..... }    省略 ....... return exposedObject; }


該方法的主要流程是:

  1. 建立bean實例

  2. 判斷做用域是否爲單例,容許循環依賴,而且當前bean正在建立,尚未建立完成。若是都知足條件,則調用addSingletonFactory將bean實例放入緩存中。

  3. 調用populateBean方法進行依賴注入

  4. 調用initializeBean方法完成對象初始化和AOP加強

咱們關注的重點能夠先放到addSingletonFactory方法上。

 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.remove(beanName); this.registeredSingletons.add(beanName); } } }

該方法的邏輯是判斷若是singletonObjects(一級緩存)中找不到實例,則將singletonFactory實例放到singletonFactories(三級緩存)中,而且移除earlySingletonObjects(二級緩存)中的實例。


createBean方法執行完以後,會調用外層的getSingleton方法


咱們重點看看這個getSingleton方法

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { if (this.singletonsCurrentlyInDestruction) { throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)");        } beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new LinkedHashSet<>(); } try { singletonObject = singletonFactory.getObject(); newSingleton = true; }        catch (IllegalStateException ex) {
singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } }


該方法邏輯很簡單,就是先從singletonObjects(一級緩存)中獲取實例,若是獲取不到,則調用singletonFactory.getObject()方法建立一個實例,而後調用addSingleton方法放入singletonObjects緩存中。


 protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }

該方法會將實例放入singletonObjects(一級緩存),而且刪除singletonFactories(二級緩存),這樣之後再調用getBean時,都能從singletonObjects(一級緩存)中獲取到實例了。


說了這麼多,再回到示例中的場景。



spring爲何要用三級緩存,而不是二級緩存?

像示例的這種狀況只用二級緩存是沒有問題的。

可是假若有這種狀況:a實例同時依賴於b實例和c實例,b實例又依賴於a實例,c實例也依賴於a實例。


a實例化時,先提早暴露objectFactorya到三級緩存,調用getBean(b)依賴注入b實例。b實例化以後,提早暴露objectFactoryb到三級緩存,調用getBean(a)依賴注入a實例,因爲提早暴露了objectFactorya,此時能夠從三級緩存中獲取到a實例, b實例完成了依賴注入,升級爲一級緩存。a實例化再getBean(c)依賴注入c實例,c實例化以後,提早暴露objectFactoryc到三級緩存,調用getBean(a)依賴注入a實例,因爲提早暴露了objectFactorya,此時能夠從三級緩存中獲取到a實例。注意這裏又要從三級緩存中獲取a實例,咱們知道三級緩存中的實例是經過調用singletonFactory.getObject()方法獲取的,返回結果每次均可能不同。若是不用二級緩存,這裏會有問題,兩次獲取的a實例不同。


總結:

    只有單例的狀況下才能解決循環依賴問題,而且allowCircularReferences要設置成true。

如下狀況仍是會出現循環依賴:

  1. 構造器注入

  2. 做用域非單例的狀況,固然在自定義做用域,本身能夠實現避免循環依賴的邏輯

  3. allowCircularReferences參數設置爲false


你們喜歡這篇文章的話,煩請關注一下 :蘇三說技術


本文分享自微信公衆號 - 蘇三說技術(gh_9f551dfec941)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索