javascript
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();上面代碼還是比較簡單的,大家都可以看懂。
我們需要了解父子容器的特點,這些是比較關鍵的,如下。
父子容器特點
使用父子容器解決開頭的問題
關鍵代碼
@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父容器中同樣是不會用到的,也是不關心一些東西,將這些相互不關心的東西隔開,可以有效的避免一些不必要的錯誤,而父子容器加載的速度也會快一些。
總結
總結
以上是生活随笔為你收集整理的Spring系列:父子容器详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rust实战入门到进阶(3)
- 下一篇: MySQL 无符号和有符号的区别