給學(xué)妹看的SpringIOC 面試題(下)
之前上篇跟學(xué)弟學(xué)妹講了一下SpringIOC的啟動(dòng)流程,今天接著給學(xué)妹聊聊DI—Dependency Injection(依賴注入)
什么是依賴注入?
依賴注入(DI)是一個(gè)過程,通過該過程,對(duì)象只能通過構(gòu)造函數(shù)參數(shù),工廠方法的參數(shù)或在構(gòu)造或創(chuàng)建對(duì)象實(shí)例后在對(duì)象實(shí)例上設(shè)置的屬性來定義其依賴關(guān)系(即,與它們一起工作的其他對(duì)象)。從工廠方法返回。
然后,容器在創(chuàng)建 bean 時(shí)注入那些依賴項(xiàng)。從根本上講,此過程是通過使用類的直接構(gòu)造或服務(wù)定位器模式來自己控制其依賴關(guān)系的實(shí)例化或位置的 Bean 本身的逆過程(因此稱為 Control Inversion)。
使用 DI 原理,代碼更簡(jiǎn)潔,當(dāng)為對(duì)象提供依賴項(xiàng)時(shí),去耦會(huì)更有效。該對(duì)象不查找其依賴項(xiàng),也不知道依賴項(xiàng)的位置或類。結(jié)果,您的類變得更易于測(cè)試,尤其是當(dāng)依賴項(xiàng)依賴于接口或抽象 Base Class 時(shí),它們?cè)试S在單元測(cè)試中使用存根或模擬實(shí)現(xiàn)。
-----------以上解釋來源Spring官方文檔
說白了依賴注入只是把bean添加到IOC容器的一種方式。
從依賴注入的方式來說整體可以分為兩大類來處理,一種是手動(dòng)方式,一種是自動(dòng)方式。
手動(dòng)方式:
- XML 資源配置元信息(比較常見)
- Java 注解配置元信息 (比較常見)
- API 配置元信息(不太常用)
自動(dòng)方式:
- Autowiring
依賴注入的方式有上面的兩種,但是也可按注入的類型來區(qū)分:
- Setter注入
- 構(gòu)造器注入
- 接口注入
- 方法注入
聊到依賴注入那么首先需要先聊聊 Autowiring Modes自動(dòng)綁定模式
Spring的官方文檔中對(duì)Autowiring Modes解釋是:
Spring 容器可以自動(dòng)裝配協(xié)作 bean 之間的關(guān)系。通過檢查 ApplicationContext 的內(nèi)容,您可以讓 Spring 自動(dòng)為您的 bean 解析協(xié)作者(其他 bean)
同時(shí)也提出了4種自動(dòng)裝配模式
- no:(默認(rèn))無自動(dòng)裝配。Bean 引用必須由ref元素定義。對(duì)于大型部署,建議不要更改默認(rèn)設(shè)置,因?yàn)槊鞔_指定協(xié)作者可以提供更好的控制和清晰度。在某種程度上,它記錄了系統(tǒng)的結(jié)構(gòu)。
- byName:按屬性名稱自動(dòng)布線。Spring 尋找與需要自動(dòng)裝配的屬性同名的 bean。例如,如果一個(gè) bean 定義被設(shè)置為按名稱自動(dòng)裝配,并且包含一個(gè)master屬性(即,它具有setMaster(..)方法),那么 Spring 將查找一個(gè)名為master的 bean 定義并使用它來設(shè)置屬性。
- byType:如果容器中恰好存在一個(gè)該屬性類型的 bean,則使該屬性自動(dòng)裝配。如果存在多個(gè)錯(cuò)誤,則會(huì)引發(fā)致命異常,這表明您可能不對(duì)該 bean 使用byType自動(dòng)裝配。如果沒有匹配的 bean,則什么也不會(huì)發(fā)生(未設(shè)置該屬性)。
- constructor:類似于byType,但適用于構(gòu)造函數(shù)參數(shù)。如果容器中不存在構(gòu)造函數(shù)參數(shù)類型的一個(gè) bean,則將引發(fā)致命錯(cuò)誤。
雖然官方文檔提出了Autowiring自動(dòng)綁定方式,但是在我們的真實(shí)的業(yè)務(wù)場(chǎng)景中,相對(duì)來說是用的比較少的,因?yàn)樗幸欢ǖ木窒扌裕襍pring官方文檔中也列出了其中的不足點(diǎn)。
自動(dòng)裝配的局限性和缺點(diǎn)(官方文檔鏈接)
- property和constructor-arg設(shè)置中的顯式依賴項(xiàng)始終會(huì)覆蓋自動(dòng)裝配。您不能自動(dòng)連接簡(jiǎn)單屬性,例如基元,Strings和Classes(以及此類簡(jiǎn)單屬性的數(shù)組)。此限制是設(shè)計(jì)使然 PS:針對(duì)這種情況可以通過另外的一種方式@value等進(jìn)行轉(zhuǎn)化來處理這個(gè)場(chǎng)景。
- 自動(dòng)裝配不如顯式接線精確。盡管如前所述,Spring 還是小心避免在可能產(chǎn)生意外結(jié)果的模棱兩可的情況下進(jìn)行猜測(cè)。SpringManagement 的對(duì)象之間的關(guān)系不再明確記錄。
- 容器內(nèi)的多個(gè) bean 定義可能與要自動(dòng)裝配的 setter 方法或構(gòu)造函數(shù)參數(shù)指定的類型匹配。對(duì)于數(shù)組,集合或Map實(shí)例,這不一定是問題。但是,對(duì)于需要單個(gè)值的依賴項(xiàng),不會(huì)任意解決此歧義。如果沒有唯一的 bean 定義可用,則引發(fā)異常。
說完這么多文檔的基礎(chǔ)知識(shí),那么接下來就是開始demo測(cè)試環(huán)節(jié),來加深理解一下上面的說的那么多到底是個(gè)啥。
Setter
先從注入的類型先分析怎么樣的一種方式叫Setter方式注入
- /構(gòu)建一個(gè)測(cè)試Service
- public class SetterServiceInjection {
- public void testMethod(String param) {
- System.out.println(param);
- }
- }
- public class SetterServiceInjectionTest {
- private SetterServiceInjection setterServiceInjection;
- // Setter方式注入
- public void setSetterServiceInjection(SetterServiceInjection setterServiceInjection) {
- this.setterServiceInjection = setterServiceInjection;
- }
- public void testMethod(){
- setterServiceInjection.testMethod("Setter方式注入");
- }
- // 測(cè)試啟動(dòng)demo
- public static void main(String[] args) {
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- //獲取IOC容器中的bean
- SetterServiceInjectionTest serviceInjectionTest = (SetterServiceInjectionTest) applicationContext.getBean("setterServiceInjectionTest");
- serviceInjectionTest.testMethod();
- // 結(jié)果打印:
- // Setter方式注入
- }
- }
xml文件配置
- <bean id="setterServiceInjection" class="com.ao.bing.demo.spring.ioc.SetterServiceInjection"/>
- <!--Setter方式-->
- <bean id="setterServiceInjectionTest" class="com.ao.bing.demo.spring.ioc.SetterServiceInjectionTest">
- <property name="setterServiceInjection" ref="setterServiceInjection"/>
- </bean>
上面是很常見的一種注入方式,而且這種方式常見于去寫一些配置文件、插件二方包、或者注入數(shù)據(jù)源信息等。
當(dāng)然Setter不是僅僅只是這一種使用方式,還可以注入對(duì)象,或者說注入一些集合信息等等。
構(gòu)造器注入
在代碼的實(shí)現(xiàn)上面構(gòu)造器和Setter方式是很相似的。還是按照上面的代碼改造一下如下所示
- private final SetterServiceInjection setterServiceInjection;
- // Setter方式注入
- // public void setSetterServiceInjection(SetterServiceInjection setterServiceInjection) {
- // this.setterServiceInjection = setterServiceInjection;
- // }
- public void testMethod(){
- setterServiceInjection.testMethod("構(gòu)造器方式注入");
- }
- //構(gòu)造器注入
- public SetterServiceInjectionTest(SetterServiceInjection setterServiceInjection){
- this.setterServiceInjection = setterServiceInjection;
- }
- <context:component-scan base-package="com.ao.bing.demo"/>
- <bean id="setterServiceInjection" class="com.ao.bing.demo.spring.ioc.SetterServiceInjection"/>
- <!--Setter方式-->
- <!-- <bean id="setterServiceInjectionTest" class="com.ao.bing.demo.spring.ioc.SetterServiceInjectionTest">-->
- <!-- <property name="setterServiceInjection" ref="setterServiceInjection"/>-->
- <!-- </bean>-->
- <bean id="setterServiceInjectionTest" class="com.ao.bing.demo.spring.ioc.SetterServiceInjectionTest">
- <constructor-arg index="0" ref="setterServiceInjection"/>
- </bean>
既然兩個(gè)代碼這么相似,為什么Spring官方還需要推薦使用這種方式呢?和Setter方式區(qū)別又是啥?
推薦原因:從定義的屬性來說添加了final修飾說明我們注入的依賴不能再變動(dòng)。其次從XML的配置bean的屬性來說,當(dāng)需要實(shí)例化setterServiceInjectionTest這個(gè)類的時(shí)候已經(jīng)實(shí)現(xiàn)了有參構(gòu)造函數(shù),那么就不會(huì)再使用默認(rèn)的構(gòu)造函數(shù),同時(shí)針對(duì)傳入的參數(shù)需要確保有這種類型的值,否則就會(huì)報(bào)錯(cuò),所以這樣就保證了依賴不會(huì)為空最后因?yàn)闃?gòu)造器傳入的參數(shù)是確定有值的,那就意味著構(gòu)造屬性是已經(jīng)完全初始化的狀態(tài),所以這也就避免了后面需要分析的循環(huán)依賴的問題。
區(qū)別
- 在Setter注入,可以將依賴項(xiàng)部分注入,構(gòu)造方法注入不能部分注入
- 使用setter注入不能保證類的所有的屬性都注入進(jìn)來。
- 在類對(duì)象相互依賴的時(shí)候可以通過Setter方式解決循環(huán)依賴問題。
接口回調(diào)注入
提供Spring中獲取容器本身的一些功能資源,就是通過實(shí)現(xiàn)一系列Spring Aware接口來實(shí)現(xiàn)具體的功能。
- BeanFactoryAware:獲取 IoC 容器 - BeanFactory
- ApplicationContextAware:獲取 Spring 應(yīng)用上下文 - ApplicationContext 對(duì)象
- EnvironmentAware:獲取 Environment 對(duì)象
- ResourceLoaderAware:獲取資源加載器 對(duì)象 - ResourceLoader
- BeanClassLoaderAware:獲取加載當(dāng)前 Bean Class 的 ClassLoader
- BeanNameAware:獲取當(dāng)前 Bean 的名稱
- MessageSourceAware:獲取 MessageSource 對(duì)象,用于 Spring 國際化
- ApplicationEventPublisherAware:獲取 ApplicationEventPublishAware 對(duì)象,用于 Spring 事件
- EmbeddedValueResolverAware:獲取 StringValueResolver 對(duì)象,用于占位符處理
上面的接口回調(diào)實(shí)現(xiàn)方式也比較簡(jiǎn)單,基本所有的bean都能實(shí)現(xiàn)Aware接口,但是實(shí)現(xiàn)Aware接口也有一定的局限性,不能進(jìn)行擴(kuò)展只能是進(jìn)行內(nèi)嵌,所以理解這就是一種內(nèi)建的回調(diào)方式。
以ApplicationContextAware實(shí)現(xiàn)代碼為例如下圖所示
- @Component
- public class SetterServiceInjectionTest implements ApplicationContextAware {
- // @Autowired
- // private SetterServiceInjection setterServiceInjection;
- private ApplicationContext applicationContext;
- public void testMethod() {
- SetterServiceInjection setterServiceInjection = (SetterServiceInjection) applicationContext.getBean("setterServiceInjection");
- setterServiceInjection.testMethod("接口回調(diào)");
- }
- public static void main(String[] args) {
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- //獲取IOC容器中的bean
- SetterServiceInjectionTest serviceInjectionTest = (SetterServiceInjectionTest) applicationContext.getBean("setterServiceInjectionTest");
- serviceInjectionTest.testMethod();
- }
- // 獲取上下文
- @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.applicationContext = applicationContext;
- }
- }
方法注入
方法注入實(shí)現(xiàn)方式可以分為四種:
- @Autowired:是Spring自帶的注解,依照類型進(jìn)行裝配。
- @Bean:產(chǎn)生一個(gè)Bean對(duì)象,然后這個(gè)Bean對(duì)象交給Spring管理。
- @Resource:@Resource`是JavaEE的標(biāo)準(zhǔn),Spring對(duì)它是兼容性的支持,依照名稱進(jìn)行裝配。
- @Inject(不常見):jsr330中的規(guī)范。
以常見的Autowired為例
- @Autowired
- private SetterServiceInjection setterServiceInjection;
- public void testMethod(){
- setterServiceInjection.testMethod("方法注入");
- }
- public static void main(String[] args) {
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- //獲取IOC容器中的bean
- SetterServiceInjectionTest serviceInjectionTest = (SetterServiceInjectionTest) applicationContext.getBean("setterServiceInjectionTest");
- serviceInjectionTest.testMethod();
- }
從上面的代碼中并不需要再寫一些構(gòu)造方法,也不用配置相關(guān)XML文件只要簡(jiǎn)單的加上@Autowired一個(gè)注解就能完成bean的相互關(guān)聯(lián)。
所以方法注入可以理解不用關(guān)心方法名稱也不用關(guān)心方法類型,只要方法上面在參數(shù)里面有相關(guān)的依賴類型同時(shí)加上@Autowired或者 @Resource 就能相關(guān)聯(lián)上。
類型選擇
上面介紹了這么多類型,那么應(yīng)該怎么合理的選擇哪個(gè)依賴的注入類型呢?
- 構(gòu)造器注入:強(qiáng)制依賴類型,低依賴。
- Setter 方法注入:非很強(qiáng)的強(qiáng)制依賴類型(無依賴順序),多依賴。
- 方法注入:常用于聲明類
- 接口回調(diào)注入:業(yè)務(wù)中常用于寫一些主鍵啥的。
合理的選擇注入類型能減少業(yè)務(wù)開發(fā)環(huán)境中的很多的問題。
在真實(shí)的業(yè)務(wù)場(chǎng)景中還會(huì)遇到另外的一個(gè)問題,就是多個(gè)類型相同的bean注冊(cè)到Spring容器中,那么僅僅使用上面的幾種方式Spring框架則會(huì)拋出NoUniqueBeanDefinitionException異常,所以為了解決上述的問題Spring提出了一個(gè)新的注解**@Qualifier**來指定哪一個(gè)bean或者實(shí)現(xiàn)bean的邏輯分組,其用法也相對(duì)來說比較加單
- public class QualifierDemo {
- @Autowired
- private List<Demo> demos; // 1 ,2,3,4 全部都有
- @Autowired
- @Qualifier
- private List<Demo> demosQualifier; // 只有 3,4
- @Autowired
- @Qualifier("demo2")
- private Demo demo1; // 只有2
- @Bean
- public Demo demo1() {
- return new Demo(1);
- }
- @Bean
- public Demo demo2() {
- return new Demo(2);
- }
- @Bean
- @Qualifier // 進(jìn)行邏輯分組
- public Demo demo3() {
- return new Demo(3);
- }
- @Bean
- @Qualifier // 進(jìn)行邏輯分組
- public Demo demo4() {
- return new Demo(4);
- }
- @Data
- public class Demo {
- private Integer id;
- public Demo (Integer id){
- this.id =id;
- }
- }
- }
通過上面的代碼就能很明確的知道沒有使用Qualifier注解的默認(rèn)就是加載了所有的,使用了Qualifier注解的demosQualifier的里面只有 demo3 和 demo4兩個(gè),同樣也可以指定使用那么bean如demo1所示。
當(dāng)然這里只介紹了Qualifier的簡(jiǎn)單實(shí)用,在Spring的官方文檔中還有一種用法就是實(shí)現(xiàn)Qualifier擴(kuò)展用法,自定義注解,了解Spring Cloud 的同學(xué)可以去看看@LoadBalanced這個(gè)注解。用法如下
- @Target({ElementType.FIELD, ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- @Inherited
- @Documented
- @Qualifier
- public @interface DemoGroup {
- }
Spring依賴注入差不多就跟大家聊完了,當(dāng)然后一些其他的一些比較少見的就不跟大家細(xì)聊了,比如說延遲依賴注入感興趣的可以小伙伴可以再去看下,推薦是使用ObjectProvider方式來處理。
總結(jié)
Spring的依賴注入用一句話來說解耦對(duì)象之間的依賴關(guān)系,通過xml方式或者注解的方式來靈活管理依賴。
看這中框架性的東西推薦大家可以去看看官方文檔,如果看不懂的英文的可以去找找中文翻譯過的,來加深自己的理解。(中文官方文檔鏈接)。
接下來剖析一下Spring中的3層緩存怎么去解決的循環(huán)依賴。
為了加深理解還給大家整理了一下幾個(gè)面試題。
構(gòu)造器注入和 Setter 注入有啥區(qū)別?更推薦什么方式?
答案已經(jīng)在文中構(gòu)造器的解釋中給說出來了
怎么解決多個(gè)類型相同的bean注冊(cè)到Spring容器的使用問題?
可以使用Qualifier注解來實(shí)現(xiàn)
參考文檔:中文官方文檔、《小馬哥核心編程》。
最近在搞的面試版PDF真的覺得還挺有意思的,等搞出來了,應(yīng)該可以讓大家面試前突擊突擊,對(duì)了面試視頻籌劃中了,這次準(zhǔn)備用不同的風(fēng)格演繹,下個(gè)月肯定能出來。
我是敖丙,你知道的越多,你不知道的越多,我們下期見。