<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試:講一講Spring中的循環(huán)依賴

          共 11553字,需瀏覽 24分鐘

           ·

          2020-08-11 22:59

          前言

          Spring中的循環(huán)依賴一直是Spring中一個很重要的話題,一方面是因為源碼中為了解決循環(huán)依賴做了很多處理,另外一方面是因為面試的時候,如果問到Spring中比較高階的問題,那么循環(huán)依賴必定逃不掉。如果你回答得好,那么這就是你的必殺技,反正,那就是面試官的必殺技,這也是取這個標(biāo)題的原因,當(dāng)然,本文的目的是為了讓你在之后的所有面試中能多一個必殺技,專門用來絕殺面試官!

          本文的核心思想就是,

          當(dāng)面試官問:

          “請講一講Spring中的循環(huán)依賴。”的時候,

          我們到底該怎么回答?

          主要分下面幾點

          1. 什么是循環(huán)依賴?
          2. 什么情況下循環(huán)依賴可以被處理?
          3. Spring是如何解決的循環(huán)依賴?

          同時本文希望糾正幾個目前業(yè)界內(nèi)經(jīng)常出現(xiàn)的幾個關(guān)于循環(huán)依賴的錯誤的說法

          1. 只有在setter方式注入的情況下,循環(huán)依賴才能解決(
          2. 三級緩存的目的是為了提高效率(

          OK,鋪墊已經(jīng)做完了,接下來我們開始正文

          什么是循環(huán)依賴?

          從字面上來理解就是A依賴B的同時B也依賴了A,就像下面這樣

          image-20200705175322521

          體現(xiàn)到代碼層次就是這個樣子

          @Component
          public?class?A?{
          ????//?A中注入了B
          ?@Autowired
          ?private?B?b;
          }

          @Component
          public?class?B?{
          ????//?B中也注入了A
          ?@Autowired
          ?private?A?a;
          }

          當(dāng)然,這是最常見的一種循環(huán)依賴,比較特殊的還有

          //?自己依賴自己
          @Component
          public?class?A?{
          ????//?A中注入了A
          ?@Autowired
          ?private?A?a;
          }

          雖然體現(xiàn)形式不一樣,但是實際上都是同一個問題----->循環(huán)依賴

          什么情況下循環(huán)依賴可以被處理?

          在回答這個問題之前首先要明確一點,Spring解決循環(huán)依賴是有前置條件的

          1. 出現(xiàn)循環(huán)依賴的Bean必須要是單例
          2. 依賴注入的方式不能全是構(gòu)造器注入的方式(很多博客上說,只能解決setter方法的循環(huán)依賴,這是錯誤的)

          其中第一點應(yīng)該很好理解,第二點:不能全是構(gòu)造器注入是什么意思呢?我們還是用代碼說話

          @Component
          public?class?A?{
          //?@Autowired
          //?private?B?b;
          ?public?A(B?b)?{

          ?}
          }


          @Component
          public?class?B?{

          //?@Autowired
          //?private?A?a;

          ?public?B(A?a){

          ?}
          }

          在上面的例子中,A中注入B的方式是通過構(gòu)造器,B中注入A的方式也是通過構(gòu)造器,這個時候循環(huán)依賴是無法被解決,如果你的項目中有兩個這樣相互依賴的Bean,在啟動時就會報出以下錯誤:

          Caused?by:?org.springframework.beans.factory.BeanCurrentlyInCreationException:?Error?creating?bean?with?name?'a':?Requested?bean?is?currently?in?creation:?Is?there?an?unresolvable?circular?reference?

          為了測試循環(huán)依賴的解決情況跟注入方式的關(guān)系,我們做如下四種情況的測試

          依賴情況依賴注入方式循環(huán)依賴是否被解決
          AB相互依賴(循環(huán)依賴)均采用setter方法注入
          AB相互依賴(循環(huán)依賴)均采用構(gòu)造器注入
          AB相互依賴(循環(huán)依賴)A中注入B的方式為setter方法,B中注入A的方式為構(gòu)造器
          AB相互依賴(循環(huán)依賴)B中注入A的方式為setter方法,A中注入B的方式為構(gòu)造器

          具體的測試代碼很簡單,我就不放了。從上面的測試結(jié)果我們可以看到,不是只有在setter方法注入的情況下循環(huán)依賴才能被解決,即使存在構(gòu)造器注入的場景下,循環(huán)依賴依然被可以被正常處理掉。

          那么到底是為什么呢?Spring到底是怎么處理的循環(huán)依賴呢?不要急,我們接著往下看

          Spring是如何解決的循環(huán)依賴?

          關(guān)于循環(huán)依賴的解決方式應(yīng)該要分兩種情況來討論

          1. 簡單的循環(huán)依賴(沒有AOP)
          2. 結(jié)合了AOP的循環(huán)依賴

          簡單的循環(huán)依賴(沒有AOP)

          我們先來分析一個最簡單的例子,就是上面提到的那個demo

          @Component
          public?class?A?{
          ????//?A中注入了B
          ?@Autowired
          ?private?B?b;
          }

          @Component
          public?class?B?{
          ????//?B中也注入了A
          ?@Autowired
          ?private?A?a;
          }

          通過上文我們已經(jīng)知道了這種情況下的循環(huán)依賴是能夠被解決的,那么具體的流程是什么呢?我們一步步分析

          首先,我們要知道Spring在創(chuàng)建Bean的時候默認是按照自然排序來進行創(chuàng)建的,所以第一步Spring會去創(chuàng)建A

          與此同時,我們應(yīng)該知道,Spring在創(chuàng)建Bean的過程中分為三步

          1. 實例化,對應(yīng)方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法

          2. 屬性注入,對應(yīng)方法:AbstractAutowireCapableBeanFactorypopulateBean方法

          3. 初始化,對應(yīng)方法:AbstractAutowireCapableBeanFactoryinitializeBean

          這些方法在之前源碼分析的文章中都做過詳細的解讀了,如果你之前沒看過我的文章,那么你只需要知道

          1. 實例化,簡單理解就是new了一個對象
          2. 屬性注入,為實例化中new出來的對象填充屬性
          3. 初始化,執(zhí)行aware接口中的方法,初始化方法,完成AOP代理

          基于上面的知識,我們開始解讀整個循環(huán)依賴處理的過程,整個流程應(yīng)該是以A的創(chuàng)建為起點,前文也說了,第一步就是創(chuàng)建A嘛!


          創(chuàng)建A的過程實際上就是調(diào)用getBean方法,這個方法有兩層含義

          1. 創(chuàng)建一個新的Bean
          2. 從緩存中獲取到已經(jīng)被創(chuàng)建的對象

          我們現(xiàn)在分析的是第一層含義,因為這個時候緩存中還沒有A嘛!

          調(diào)用getSingleton(beanName)

          首先調(diào)用getSingleton(a)方法,這個方法又會調(diào)用getSingleton(beanName, true),在上圖中我省略了這一步

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

          getSingleton(beanName, true)這個方法實際上就是到緩存中嘗試去獲取Bean,整個緩存分為三級

          1. singletonObjects,一級緩存,存儲的是所有創(chuàng)建好了的單例Bean
          2. earlySingletonObjects,完成實例化,但是還未進行屬性注入及初始化的對象
          3. singletonFactories,提前暴露的一個單例工廠,二級緩存中存儲的就是從這個工廠中獲取到的對象

          因為A是第一次被創(chuàng)建,所以不管哪個緩存中必然都是沒有的,因此會進入getSingleton的另外一個重載方法getSingleton(beanName, singletonFactory)

          調(diào)用getSingleton(beanName, singletonFactory)

          這個方法就是用來創(chuàng)建Bean的,其源碼如下:

          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)?{

          ????????????//?....
          ????????????//?省略異常處理及日志
          ????????????//?....

          ????????????//?在單例對象創(chuàng)建前先做一個標(biāo)記
          ????????????//?將beanName放入到singletonsCurrentlyInCreation這個集合中
          ????????????//?標(biāo)志著這個單例Bean正在創(chuàng)建
          ????????????//?如果同一個單例Bean多次被創(chuàng)建,這里會拋出異常
          ????????????beforeSingletonCreation(beanName);
          ????????????boolean?newSingleton?=?false;
          ????????????boolean?recordSuppressedExceptions?=?(this.suppressedExceptions?==?null);
          ????????????if?(recordSuppressedExceptions)?{
          ????????????????this.suppressedExceptions?=?new?LinkedHashSet<>();
          ????????????}
          ????????????try?{
          ????????????????//?上游傳入的lambda在這里會被執(zhí)行,調(diào)用createBean方法創(chuàng)建一個Bean后返回
          ????????????????singletonObject?=?singletonFactory.getObject();
          ????????????????newSingleton?=?true;
          ????????????}
          ????????????//?...
          ????????????//?省略catch異常處理
          ????????????//?...
          ????????????finally?{
          ????????????????if?(recordSuppressedExceptions)?{
          ????????????????????this.suppressedExceptions?=?null;
          ????????????????}
          ????????????????//?創(chuàng)建完成后將對應(yīng)的beanName從singletonsCurrentlyInCreation移除
          ????????????????afterSingletonCreation(beanName);
          ????????????}
          ????????????if?(newSingleton)?{
          ????????????????//?添加到一級緩存singletonObjects中
          ????????????????addSingleton(beanName,?singletonObject);
          ????????????}
          ????????}
          ????????return?singletonObject;
          ????}
          }

          上面的代碼我們主要抓住一點,通過createBean方法返回的Bean最終被放到了一級緩存,也就是單例池中。

          那么到這里我們可以得出一個結(jié)論:一級緩存中存儲的是已經(jīng)完全創(chuàng)建好了的單例Bean

          調(diào)用addSingletonFactory方法

          如下圖所示:


          在完成Bean的實例化后,屬性注入之前Spring將Bean包裝成一個工廠添加進了三級緩存中,對應(yīng)源碼如下:

          //?這里傳入的參數(shù)也是一個lambda表達式,()?->?getEarlyBeanReference(beanName,?mbd,?bean)
          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);
          ????????}
          ????}
          }

          這里只是添加了一個工廠,通過這個工廠(ObjectFactory)的getObject方法可以得到一個對象,而這個對象實際上就是通過getEarlyBeanReference這個方法創(chuàng)建的。那么,什么時候會去調(diào)用這個工廠的getObject方法呢?這個時候就要到創(chuàng)建B的流程了。

          當(dāng)A完成了實例化并添加進了三級緩存后,就要開始為A進行屬性注入了,在注入時發(fā)現(xiàn)A依賴了B,那么這個時候Spring又會去getBean(b),然后反射調(diào)用setter方法完成屬性注入。


          因為B需要注入A,所以在創(chuàng)建B的時候,又會去調(diào)用getBean(a),這個時候就又回到之前的流程了,但是不同的是,之前的getBean是為了創(chuàng)建Bean,而此時再調(diào)用getBean不是為了創(chuàng)建了,而是要從緩存中獲取,因為之前A在實例化后已經(jīng)將其放入了三級緩存singletonFactories中,所以此時getBean(a)的流程就是這樣子了


          從這里我們可以看出,注入到B中的A是通過getEarlyBeanReference方法提前暴露出去的一個對象,還不是一個完整的Bean,那么getEarlyBeanReference到底干了啥了,我們看下它的源碼

          protected?Object?getEarlyBeanReference(String?beanName,?RootBeanDefinition?mbd,?Object?bean)?{
          ????Object?exposedObject?=?bean;
          ????if?(!mbd.isSynthetic()?&&?hasInstantiationAwareBeanPostProcessors())?{
          ????????for?(BeanPostProcessor?bp?:?getBeanPostProcessors())?{
          ????????????if?(bp?instanceof?SmartInstantiationAwareBeanPostProcessor)?{
          ????????????????SmartInstantiationAwareBeanPostProcessor?ibp?=?(SmartInstantiationAwareBeanPostProcessor)?bp;
          ????????????????exposedObject?=?ibp.getEarlyBeanReference(exposedObject,?beanName);
          ????????????}
          ????????}
          ????}
          ????return?exposedObject;
          }

          它實際上就是調(diào)用了后置處理器的getEarlyBeanReference,而真正實現(xiàn)了這個方法的后置處理器只有一個,就是通過@EnableAspectJAutoProxy注解導(dǎo)入的AnnotationAwareAspectJAutoProxyCreator也就是說如果在不考慮AOP的情況下,上面的代碼等價于:

          protected?Object?getEarlyBeanReference(String?beanName,?RootBeanDefinition?mbd,?Object?bean)?{
          ????Object?exposedObject?=?bean;
          ????return?exposedObject;
          }

          也就是說這個工廠啥都沒干,直接將實例化階段創(chuàng)建的對象返回了!所以說在不考慮AOP的情況下三級緩存有用嘛?講道理,真的沒什么用,我直接將這個對象放到二級緩存中不是一點問題都沒有嗎?如果你說它提高了效率,那你告訴我提高的效率在哪?


          那么三級緩存到底有什么作用呢?不要急,我們先把整個流程走完,在下文結(jié)合AOP分析循環(huán)依賴的時候你就能體會到三級緩存的作用!

          到這里不知道小伙伴們會不會有疑問,B中提前注入了一個沒有經(jīng)過初始化的A類型對象不會有問題嗎?

          答:不會

          這個時候我們需要將整個創(chuàng)建A這個Bean的流程走完,如下圖:


          從上圖中我們可以看到,雖然在創(chuàng)建B時會提前給B注入了一個還未初始化的A對象,但是在創(chuàng)建A的流程中一直使用的是注入到B中的A對象的引用,之后會根據(jù)這個引用對A進行初始化,所以這是沒有問題的。

          結(jié)合了AOP的循環(huán)依賴

          之前我們已經(jīng)說過了,在普通的循環(huán)依賴的情況下,三級緩存沒有任何作用。三級緩存實際上跟Spring中的AOP相關(guān),我們再來看一看getEarlyBeanReference的代碼:

          protected?Object?getEarlyBeanReference(String?beanName,?RootBeanDefinition?mbd,?Object?bean)?{
          ????Object?exposedObject?=?bean;
          ????if?(!mbd.isSynthetic()?&&?hasInstantiationAwareBeanPostProcessors())?{
          ????????for?(BeanPostProcessor?bp?:?getBeanPostProcessors())?{
          ????????????if?(bp?instanceof?SmartInstantiationAwareBeanPostProcessor)?{
          ????????????????SmartInstantiationAwareBeanPostProcessor?ibp?=?(SmartInstantiationAwareBeanPostProcessor)?bp;
          ????????????????exposedObject?=?ibp.getEarlyBeanReference(exposedObject,?beanName);
          ????????????}
          ????????}
          ????}
          ????return?exposedObject;
          }

          如果在開啟AOP的情況下,那么就是調(diào)用到AnnotationAwareAspectJAutoProxyCreatorgetEarlyBeanReference方法,對應(yīng)的源碼如下:

          public?Object?getEarlyBeanReference(Object?bean,?String?beanName)?{
          ????Object?cacheKey?=?getCacheKey(bean.getClass(),?beanName);
          ????this.earlyProxyReferences.put(cacheKey,?bean);
          ????//?如果需要代理,返回一個代理對象,不需要代理,直接返回當(dāng)前傳入的這個bean對象
          ????return?wrapIfNecessary(bean,?beanName,?cacheKey);
          }

          回到上面的例子,我們對A進行了AOP代理的話,那么此時getEarlyBeanReference將返回一個代理后的對象,而不是實例化階段創(chuàng)建的對象,這樣就意味著B中注入的A將是一個代理對象而不是A的實例化階段創(chuàng)建后的對象。

          看到這個圖你可能會產(chǎn)生下面這些疑問

          1. 在給B注入的時候為什么要注入一個代理對象?

          答:當(dāng)我們對A進行了AOP代理時,說明我們希望從容器中獲取到的就是A代理后的對象而不是A本身,因此把A當(dāng)作依賴進行注入時也要注入它的代理對象

          1. 明明初始化的時候是A對象,那么Spring是在哪里將代理對象放入到容器中的呢?


          在完成初始化后,Spring又調(diào)用了一次getSingleton方法,這一次傳入的參數(shù)又不一樣了,false可以理解為禁用三級緩存,前面圖中已經(jīng)提到過了,在為B中注入A時已經(jīng)將三級緩存中的工廠取出,并從工廠中獲取到了一個對象放入到了二級緩存中,所以這里的這個getSingleton方法做的時間就是從二級緩存中獲取到這個代理后的A對象。exposedObject == bean可以認為是必定成立的,除非你非要在初始化階段的后置處理器中替換掉正常流程中的Bean,例如增加一個后置處理器:

          @Component
          public?class?MyPostProcessor?implements?BeanPostProcessor?{
          ?@Override
          ?public?Object?postProcessAfterInitialization(Object?bean,?String?beanName)?throws?BeansException?{
          ??if?(beanName.equals("a"))?{
          ???return?new?A();
          ??}
          ??return?bean;
          ?}
          }

          不過,請不要做這種騷操作,徒增煩惱!

          1. 初始化的時候是對A對象本身進行初始化,而容器中以及注入到B中的都是代理對象,這樣不會有問題嗎?

          答:不會,這是因為不管是cglib代理還是jdk動態(tài)代理生成的代理類,內(nèi)部都持有一個目標(biāo)類的引用,當(dāng)調(diào)用代理對象的方法時,實際會去調(diào)用目標(biāo)對象的方法,A完成初始化相當(dāng)于代理對象自身也完成了初始化

          1. 三級緩存為什么要使用工廠而不是直接使用引用?換而言之,為什么需要這個三級緩存,直接通過二級緩存暴露一個引用不行嗎?

          答:這個工廠的目的在于延遲對實例化階段生成的對象的代理,只有真正發(fā)生循環(huán)依賴的時候,才去提前生成代理對象,否則只會創(chuàng)建一個工廠并將其放入到三級緩存中,但是不會去通過這個工廠去真正創(chuàng)建對象

          我們思考一種簡單的情況,就以單獨創(chuàng)建A為例,假設(shè)AB之間現(xiàn)在沒有依賴關(guān)系,但是A被代理了,這個時候當(dāng)A完成實例化后還是會進入下面這段代碼:

          //?A是單例的,mbd.isSingleton()條件滿足
          // allowCircularReferences:這個變量代表是否允許循環(huán)依賴,默認是開啟的,條件也滿足
          // isSingletonCurrentlyInCreation:正在在創(chuàng)建A,也滿足
          //?所以earlySingletonExposure=true
          boolean?earlySingletonExposure?=?(mbd.isSingleton()?&&?this.allowCircularReferences?&&
          ??????????????????????????????????isSingletonCurrentlyInCreation(beanName));
          //?還是會進入到這段代碼中
          if?(earlySingletonExposure)?{
          ?//?還是會通過三級緩存提前暴露一個工廠對象
          ????addSingletonFactory(beanName,?()?->?getEarlyBeanReference(beanName,?mbd,?bean));
          }

          看到了吧,即使沒有循環(huán)依賴,也會將其添加到三級緩存中,而且是不得不添加到三級緩存中,因為到目前為止Spring也不能確定這個Bean有沒有跟別的Bean出現(xiàn)循環(huán)依賴。

          假設(shè)我們在這里直接使用二級緩存的話,那么意味著所有的Bean在這一步都要完成AOP代理。這樣做有必要嗎?

          不僅沒有必要,而且違背了Spring在結(jié)合AOP跟Bean的生命周期的設(shè)計!Spring結(jié)合AOP跟Bean的生命周期本身就是通過AnnotationAwareAspectJAutoProxyCreator這個后置處理器來完成的,在這個后置處理的postProcessAfterInitialization方法中對初始化后的Bean完成AOP代理。如果出現(xiàn)了循環(huán)依賴,那沒有辦法,只有給Bean先創(chuàng)建代理,但是沒有出現(xiàn)循環(huán)依賴的情況下,設(shè)計之初就是讓Bean在生命周期的最后一步完成代理而不是在實例化后就立馬完成代理。

          三級緩存真的提高了效率了嗎?

          現(xiàn)在我們已經(jīng)知道了三級緩存的真正作用,但是這個答案可能還無法說服你,所以我們再最后總結(jié)分析一波,三級緩存真的提高了效率了嗎?分為兩點討論:

          1. 沒有進行AOP的Bean間的循環(huán)依賴

          從上文分析可以看出,這種情況下三級緩存根本沒用!所以不會存在什么提高了效率的說法

          1. 進行了AOP的Bean間的循環(huán)依賴

          就以我們上的A、B為例,其中A被AOP代理,我們先分析下使用了三級緩存的情況下,A、B的創(chuàng)建流程


          假設(shè)不使用三級緩存,直接在二級緩存中


          上面兩個流程的唯一區(qū)別在于為A對象創(chuàng)建代理的時機不同,在使用了三級緩存的情況下為A創(chuàng)建代理的時機是在B中需要注入A的時候,而不使用三級緩存的話在A實例化后就需要馬上為A創(chuàng)建代理然后放入到二級緩存中去。對于整個A、B的創(chuàng)建過程而言,消耗的時間是一樣的

          綜上,不管是哪種情況,三級緩存提高了效率這種說法都是錯誤的!

          總結(jié)

          面試官:”Spring是如何解決的循環(huán)依賴?“

          答:Spring通過三級緩存解決了循環(huán)依賴,其中一級緩存為單例池(singletonObjects),二級緩存為早期曝光對象earlySingletonObjects,三級緩存為早期曝光對象工廠(singletonFactories)。當(dāng)A、B兩個類發(fā)生循環(huán)引用時,在A完成實例化后,就使用實例化后的對象去創(chuàng)建一個對象工廠,并添加到三級緩存中,如果A被AOP代理,那么通過這個工廠獲取到的就是A代理后的對象,如果A沒有被AOP代理,那么這個工廠獲取到的就是A實例化的對象。當(dāng)A進行屬性注入時,會去創(chuàng)建B,同時B又依賴了A,所以創(chuàng)建B的同時又會去調(diào)用getBean(a)來獲取需要的依賴,此時的getBean(a)會從緩存中獲取,第一步,先獲取到三級緩存中的工廠;第二步,調(diào)用對象工工廠的getObject方法來獲取到對應(yīng)的對象,得到這個對象后將其注入到B中。緊接著B會走完它的生命周期流程,包括初始化、后置處理器等。當(dāng)B創(chuàng)建完后,會將B再注入到A中,此時A再完成它的整個生命周期。至此,循環(huán)依賴結(jié)束!

          面試官:”為什么要使用三級緩存呢?二級緩存能解決循環(huán)依賴嗎?“

          答:如果要使用二級緩存解決循環(huán)依賴,意味著所有Bean在實例化后就要完成AOP代理,這樣違背了Spring設(shè)計的原則,Spring在設(shè)計之初就是通過AnnotationAwareAspectJAutoProxyCreator這個后置處理器來在Bean生命周期的最后一步來完成AOP代理,而不是在實例化后就立馬進行AOP代理。

          一道思考題

          為什么在下表中的第三種情況的循環(huán)依賴能被解決,而第四種情況不能被解決呢?

          提示:Spring在創(chuàng)建Bean時默認會根據(jù)自然排序進行創(chuàng)建,所以A會先于B進行創(chuàng)建

          依賴情況依賴注入方式循環(huán)依賴是否被解決
          AB相互依賴(循環(huán)依賴)均采用setter方法注入
          AB相互依賴(循環(huán)依賴)均采用構(gòu)造器注入
          AB相互依賴(循環(huán)依賴)A中注入B的方式為setter方法,B中注入A的方式為構(gòu)造器
          AB相互依賴(循環(huán)依賴)B中注入A的方式為setter方法,A中注入B的方式為構(gòu)造器


          往期推薦

          公司用了 6 年的分布式鎖,很是牛逼啊!

          IDEA不愧為神器,結(jié)合Groovy腳本,簡直無敵!

          只需4步,自己搞個 Spring Boot Starter !

          說說你知道的數(shù)據(jù)庫常用架構(gòu)方案?

          ArrayList 為什么要實現(xiàn) RandomAccess 接口?



          每日學(xué)干貨,一起進大廠

          關(guān)注我,不迷路

          瀏覽 37
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色婷婷综合激情国产日韩 | 国产农村乱╳╳╳乱免费下载 | 黄色视频高清无码 | 影音先锋一二三区 | 青娱乐精品视频在线 |