日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring系列:父子容器详解

發布時間:2025/3/12 javascript 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring系列:父子容器详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

又一次被面試官帶到坑里面了。

面試官:springmvc用過么?

我:用過啊,經常用呢

面試官:springmvc中為什么需要用父子容器?

我:嗯。。。沒聽明白你說的什么。

面試官:就是controller層交給一個spring容器加載,其他的service和dao層交給另外一個spring容器加載,web.xml中有這塊配置,這兩個容器組成了父子容器的關系。

我:哦,原來是這塊啊,我想起來了,我看大家都這么用,所以我也這么用

面試官:有沒有考慮過為什么?

我:我在網上看大家都這么用,所以我也這么用了,具體也不知道為什么,不過用起來還挺順手的

面試官:如果只用一個容器可以么,所有的配置都交給一個spring容器加載?

我:應該不行吧!

面試官:確定不行么?

我:讓我想一會。。。。。我感覺是可以的,也可以正常運行。

面試官:那我們又回到了開頭的問題,為什么要用父子容器呢?

我:我叫你哥好么,別這么玩我了,被你繞暈了?

面試官:好吧,你回去試試看吧,下次再來告訴我,出門右轉,不送!

我:臉色變綠了,灰頭土臉的走了。

回去之后,我好好研究了一番,下次準備再去給面試官一點顏色看看。

主要的問題

  • 什么是父子容器?
  • 為什么需要用父子容器?
  • 父子容器如何使用?
  • 下面我們就來探討探討。

    我們先來看一個案例

    系統中有2個模塊:module1和module2,兩個模塊是獨立開發的,module2會使用到module1中的一些類,module1會將自己打包為jar提供給module2使用,我們來看一下這2個模塊的代碼。

    模塊1

    放在module1包中,有3個類

    Service1

    package com.javacode2018.lesson002.demo17.module1;import org.springframework.stereotype.Component;@Component public class Service1 {public String m1() {return "我是module1中的Servce1中的m1方法";} }

    Service2

    package com.javacode2018.lesson002.demo17.module1;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class Service2 {@Autowiredprivate com.javacode2018.lesson002.demo17.module1.Service1 service1; //@1public String m1() { //@2return this.service1.m1();}}

    上面2個類,都標注了@Compontent注解,會被spring注冊到容器中。

    @1:Service2中需要用到Service1,標注了@Autowired注解,會通過spring容器注入進來

    @2:Service2中有個m1方法,內部會調用service的m1方法。

    來個spring配置類:Module1Config

    package com.javacode2018.lesson002.demo17.module1;import org.springframework.context.annotation.ComponentScan;@ComponentScan public class Module1Config { }

    上面使用了@CompontentScan注解,會自動掃描當前類所在的包中的所有類,將標注有@Compontent注解的類注冊到spring容器,即Service1和Service2會被注冊到spring容器。

    再來看模塊2

    放在module2包中,也是有3個類,和模塊1中的有點類似。

    Service1

    模塊2中也定義了一個Service1,內部提供了一個m2方法,如下:

    package com.javacode2018.lesson002.demo17.module2;import org.springframework.stereotype.Component;@Component public class Service1 {public String m2() {return "我是module2中的Servce1中的m2方法";} }

    Service3

    package com.javacode2018.lesson002.demo17.module2;import com.javacode2018.lesson002.demo17.module1.Service2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component;@Component public class Service3 {//使用模塊2中的Service1@Autowiredprivate com.javacode2018.lesson002.demo17.module2.Service1 service1; //@1//使用模塊1中的Service2@Autowiredprivate com.javacode2018.lesson002.demo17.module1.Service2 service2; //@2public String m1() {return this.service2.m1();}public String m2() {return this.service1.m2();}}

    @1:使用module2中的Service1

    @2:使用module1中的Service2

    先來思考一個問題

    上面的這些類使用spring來操作會不會有問題?會有什么問題?

    這個問題還是比較簡單的,大部分人都可以看出來,會報錯,因為兩個模塊中都有Service1,被注冊到spring容器的時候,bean名稱會沖突,導致注冊失敗。

    來個測試類,看一下效果

    package com.javacode2018.lesson002.demo17;import com.javacode2018.lesson001.demo21.Config; import com.javacode2018.lesson002.demo17.module1.Module1Config; import com.javacode2018.lesson002.demo17.module2.Module2Config; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class ParentFactoryTest {@Testpublic void test1() {//定義容器AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();//注冊beancontext.register(Module1Config.class, Module2Config.class); //@1//啟動容器context.refresh();} }

    @1:將Module1Config、Module2Config注冊到容器,spring內部會自動解析這兩個類上面的注解,即:@CompontentScan注解,然后會進行包掃描,將標注了@Compontent的類注冊到spring容器。

    運行test1輸出

    下面是部分輸出:

    Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation- specified bean name 'service1' for bean class [com.javacode2018.lesson002.demo17.module2.Service1] conflicts with existing, non-compatible bean definition of same name and class [com.javacode2018.lesson002.demo17.module1.Service1]

    service1這個bean的名稱沖突了。

    那么我們如何解決?

    對module1中的Service1進行修改?這個估計是行不通的,module1是別人以jar的方式提供給我們的,源碼我們是無法修改的。

    而module2是我們自己的開發的,里面的東西我們可以隨意調整,那么我們可以去修改一下module2中的Service1,可以修改一下類名,或者修改一下這個bean的名稱,此時是可以解決問題的。

    不過大家有沒有想過一個問題:如果我們的模塊中有很多類都出現了這種問題,此時我們一個個去重構,還是比較痛苦的,并且代碼重構之后,還涉及到重新測試的問題,工作量也是蠻大的,這些都是風險。

    而spring中的父子容器就可以很好的解決上面這種問題。

    什么是父子容器

    創建spring容器的時候,可以給當前容器指定一個父容器。

    BeanFactory的方式

    //創建父容器parentFactory DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory(); //創建一個子容器childFactory DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory(); //調用setParentBeanFactory指定父容器 childFactory.setParentBeanFactory(parentFactory);

    ApplicationContext的方式

    //創建父容器 AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext(); //啟動父容器 parentContext.refresh();//創建子容器 AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext(); //給子容器設置父容器 childContext.setParent(parentContext); //啟動子容器 childContext.refresh();

    上面代碼還是比較簡單的,大家都可以看懂。

    我們需要了解父子容器的特點,這些是比較關鍵的,如下。

    父子容器特點

  • 父容器和子容器是相互隔離的,他們內部可以存在名稱相同的bean
  • 子容器可以訪問父容器中的bean,而父容器不能訪問子容器中的bean
  • 調用子容器的getBean方法獲取bean的時候,會沿著當前容器開始向上面的容器進行查找,直到找到對應的bean為止
  • 子容器中可以通過任何注入方式注入父容器中的bean,而父容器中是無法注入子容器中的bean,原因是第2點
  • 使用父子容器解決開頭的問題

    關鍵代碼

    @Test public void test2() {//創建父容器AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();//向父容器中注冊Module1Config配置類parentContext.register(Module1Config.class);//啟動父容器parentContext.refresh();//創建子容器AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();//向子容器中注冊Module2Config配置類childContext.register(Module2Config.class);//給子容器設置父容器childContext.setParent(parentContext);//啟動子容器childContext.refresh();//從子容器中獲取Service3Service3 service3 = childContext.getBean(Service3.class);System.out.println(service3.m1());System.out.println(service3.m2()); }

    運行輸出

    我是module1中的Servce1中的m1方法 我是module2中的Servce1中的m2方法

    這次正常了。

    父子容器使用注意點

    我們使用容器的過程中,經常會使用到的一些方法,這些方法通常會在下面的兩個接口中

    org.springframework.beans.factory.BeanFactory org.springframework.beans.factory.ListableBeanFactory

    這兩個接口中有很多方法,這里就不列出來了,大家可以去看一下源碼,這里要說的是使用父子容器的時候,有些需要注意的地方。

    BeanFactory接口,是spring容器的頂層接口,這個接口中的方法是支持容器嵌套結構查找的,比如我們常用的getBean方法,就是這個接口中定義的,調用getBean方法的時候,會從沿著當前容器向上查找,直到找到滿足條件的bean為止。

    而ListableBeanFactory這個接口中的方法是不支持容器嵌套結構查找的,比如下面這個方法

    String[] getBeanNamesForType(@Nullable Class<?> type)

    獲取指定類型的所有bean名稱,調用這個方法的時候只會返回當前容器中符合條件的bean,而不會去遞歸查找其父容器中的bean。

    來看一下案例代碼,感受一下:

    @Test public void test3() {//創建父容器parentFactoryDefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();//向父容器parentFactory注冊一個bean[userName->"路人甲Java"]parentFactory.registerBeanDefinition("userName",BeanDefinitionBuilder.genericBeanDefinition(String.class).addConstructorArgValue("路人甲Java").getBeanDefinition());//創建一個子容器childFactoryDefaultListableBeanFactory childFactory = new DefaultListableBeanFactory();//調用setParentBeanFactory指定父容器childFactory.setParentBeanFactory(parentFactory);//向子容器parentFactory注冊一個bean[address->"上海"]childFactory.registerBeanDefinition("address",BeanDefinitionBuilder.genericBeanDefinition(String.class).addConstructorArgValue("上海").getBeanDefinition());System.out.println("獲取bean【userName】:" + childFactory.getBean("userName"));//@1System.out.println(Arrays.asList(childFactory.getBeanNamesForType(String.class))); //@2 }

    上面定義了2個容器

    父容器:parentFactory,內部定義了一個String類型的bean:userName->路人甲Java

    子容器:childFactory,內部也定義了一個String類型的bean:address->上海

    @1:調用子容器的getBean方法,獲取名稱為userName的bean,userName這個bean是在父容器中定義的,而getBean方法是BeanFactory接口中定義的,支持容器層次查找,所以getBean是可以找到userName這個bean的

    @2:調用子容器的getBeanNamesForType方法,獲取所有String類型的bean名稱,而getBeanNamesForType方法是ListableBeanFactory接口中定義的,這個接口中方法不支持層次查找,只會在當前容器中查找,所以這個方法只會返回子容器的address

    我們來運行一下看看效果:

    獲取bean【userName】:路人甲Java [address]

    結果和分析的一致。

    那么問題來了:有沒有方式解決ListableBeanFactory接口不支持層次查找的問題?

    spring中有個工具類就是解決這個問題的,如下:

    org.springframework.beans.factory.BeanFactoryUtils

    這個類中提供了很多靜態方法,有很多支持層次查找的方法,源碼你們可以去細看一下,名稱中包含有Ancestors的都是支持層次查找的。

    在test2方法中加入下面的代碼:

    //層次查找所有符合類型的bean名稱 String[] beanNamesForTypeIncludingAncestors = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(childFactory, String.class); System.out.println(Arrays.asList(beanNamesForTypeIncludingAncestors));Map<String, String> beansOfTypeIncludingAncestors = BeanFactoryUtils.beansOfTypeIncludingAncestors(childFactory, String.class); System.out.println(Arrays.asList(beansOfTypeIncludingAncestors));

    運行輸出

    [address, userName] [{address=上海, userName=路人甲Java}]

    查找過程是按照層次查找所有滿足條件的bean。

    回頭看一下springmvc父子容器的問題

    問題1:springmvc中只使用一個容器是否可以?

    只使用一個容器是可以正常運行的。

    問題2:那么springmvc中為什么需要用到父子容器?

    通常我們使用springmvc的時候,采用3層結構,controller層,service層,dao層;父容器中會包含dao層和service層,而子容器中包含的只有controller層;這2個容器組成了父子容器的關系,controller層通常會注入service層的bean。

    采用父子容器可以避免有些人在service層去注入controller層的bean,導致整個依賴層次是比較混亂的。

    父容器和子容器的需求也是不一樣的,比如父容器中需要有事務的支持,會注入一些支持事務的擴展組件,而子容器中controller完全用不到這些,對這些并不關心,子容器中需要注入一下springmvc相關的bean,而這些bean父容器中同樣是不會用到的,也是不關心一些東西,將這些相互不關心的東西隔開,可以有效的避免一些不必要的錯誤,而父子容器加載的速度也會快一些。

    總結

  • 本文需掌握父子容器的用法,了解父子容器的特點:子容器可以訪問父容器中bean,父容器無法訪問子容器中的bean
  • BeanFactory接口支持層次查找
  • ListableBeanFactory接口不支持層次查找
  • BeanFactoryUtils工具類中提供了一些非常實用的方法,比如支持bean層次查找的方法等等
  • 總結

    以上是生活随笔為你收集整理的Spring系列:父子容器详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。