按照劉Java的順序,應該是從基于XML的DI開始接著上面的關于IoC容器裝配。主要介紹學習Spring的XML基于注解的詳細配置。
第一步是搭建一個Spring的基礎工程(maven管理),通過IoC機制獲取IoC容器的對象。
創建maven工程并在pom文件并添加依賴:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>liujavaspringioc</groupId><artifactId>com.zp.liu</artifactId><version>1.0-SNAPSHOT</version><properties><spring-framework.version>5.2.8.RELEASE</spring-framework.version></properties><dependencies><!--spring 核心組件所需依賴--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring-framework.version}</version><scope>compile</scope></dependency></dependencies></project>
添加這一個依賴就可以了,這個spring-context是spring framework的坐標依賴,引入它其他的framework的核心依賴就會引入進來。
添加配置文件
在resource下創建一個配置文件,命名建議spring-context.xml或者applicationContext.xml.(這個resource目錄是maven工程自帶的,在src下面,不要自己建,這也是maven工程的一個特點,規范了工程的目錄結構。)
IoC容器
Spring的核心機制是IoC(Inversion of Control),控制反轉。
就是將對象的創建和屬性設置set的方式反轉,不再是人來直接創建。
以前是開發人員創建對象,為對象賦值。現在有了Spring,就把對象和屬性的創建和管理由Spring來管理,Spring來處理對象生命周期,屬性控制以及其他對象之間的關系,這樣類和類之間就結耦,這些類實例還能做到復用。‘’
IoC也是DI的意思,dependcy injection 依賴注入,就是對象獲取它需要依賴的對象的方式,不是主動去找,而是被動通過IoC容器來找到對應的依賴,給這些被依賴的注入進來。
Spring中管理的對象的容器稱為IoC容器,IoC容器負責實例化,配置和裝配Bean。
(org.Springframework.beans和orgSpringframework.context包是Spring IoC容器的基礎)
IoC容器是一個抽象的概念,具體代碼的實現,最頂層是BeanFactory接口和他的實現類,對應就是他的IoC容器的實現。
BeanFactory僅作為IoC容器的超級接口,真正可用的容器不會用它,真正的容器的實現是對應的一系列的實現類,BeanFactory最主要的兩個容器的實現,DefaultListableBeanFactory和ApplicationContext。
DefaultListableBeanFactory
DefaultListableBeanFactory是IoC容器的真正實現,也是原始的默認的IoC容器實現,通常作為自定義的BeanFactory的父類的。通過Resource來加載Spring的xml文件,Bean信息回加載到IoC容器,啟動IoC容器就可以使用getBean方法從IoC容器中獲取Bean對象。
DefalutListableBeanFactory加載Bean的方式稱為‘懶加載’,懶加載就是只有容器啟動才會把配置文件的配置信息加載到容器,但是不會創建對象,getBean才會創建所需要的對象。
(懶加載就是容器啟動只讀取配置信息,getBean的時候才去創建對象。)
XMLBeanFactory容器,繼承了DefaulyListableBeanFzctory,是集本身就是對DefaultListableBeanFactory和XMLBeanDefintionReader的封裝調用。
Spring中還有一個XMLBeanFactory,繼承了DefaultBeanListableBeanFactory,實際上XMLBeanfactory是XMLBeanDefinitionReader和XMLBeanFactory的封裝,但是在Spring3.1以后就被棄用。
DefaultBeanListableFactory和BeanXMLFactory都只適合單體應用IoC容器。(現在基本都是分布式,這種單體應用就不適用了。)
ApplicationContext接口
這時候ApplicationContext這個目前用的最多的IoC容器,ApplicationContext時BeanFactory的子接口,繼承了BeanFactory所有的功能,實例化Bean組裝和配置。他自己又有單獨的AOP集成,消息資源處理(i18n,國際化),事件發布以及應用層的context的上下文。
Spring推薦我們用ApplicationContext做容器
ApplicationContext是非消極加載的,就是在容器一啟動,就把配置所有的默認對象創建起來,并放在容器里。
(一啟動就加載就是餓加載。getBean的時候才加載就是懶加載。對比餓漢模式和懶漢模式)
Bean
我們經常說Bean,意思就是交給容器管理的對象就是Bean。或者換個說法,Bean是SpringIoC容器實例化,管理,組裝和配置的對象。
還有一個我們經常說的元數據,也有說配置元數據。就是我們定義的Bean的信息以及他的依賴關系,這個配置元數據,可以XML,可以是注釋,也可以是Java代碼(構造器)來表示。
(1)基于XML的配置,現在在被替代
(2)基于注解的配置,從Spring2.5開始,對應的注解@Autowired,@PostConstruct,@PreDestory等
(3)基于Java的配置,從Spring3開始,可以用Java文件替代XML配置Bean,比如@Configuration,@Bean,@import和@dependOn注解
定義的配置元數據,加載到Spring容器里,會被轉換成BeanDefinition,BeanDefinition會記錄所有解析道Bean的定義。這種的好處就是沒必要,沒必要用到配置信息解析一遍配置數據。用到配置數據就不用再去解析一遍配置數據。
三者關系是這個鏈條:
配置元數據 (xml <bean …> ,注解,Java配置文件) -----》
BeanDefinition(指導容器的Bean實例化對象)-----》
Bean(容器生成管理的對象)
BeanDefinition包含以下信息:
(1)包限定類名,通常定義Bean的實現類
(2)Bean所限定的包的全限定類名,表示Bean的實際類型
(3)Bean行為的配置元素,用于說明Bean在容器中的作用范圍,生命周期限定函數
(4)對Bean所需要其他依賴的Bean類的引用,這些引用也被叫做依賴項和協作者。
(5)在新創建的對象設置其他屬性,連接池中Bean的線程數
(6)其他屬性
配置元數據被加載成BeanDefinition被容器加載,然后在容器啟動中,IoC容器啟動中來完成依賴注入。
基于XML的Bean裝配
XML是配置元數據的最基礎的配置,Spring為bean的配置提供多個標簽和多個屬性。
標簽作為和其他標簽的容器,作為文檔中的根元素。
多個XML的配置文件
可以用多個配置文件,區分不同的模塊。
(1)在使用ApplicationContext的時候,在構造函數中傳遞多個XM文件配置文件位置參數
(2)除了構造函數,還可以在XML配置文件,使用一個或者多個標簽將其他的XML配置文件導入到一個配置文件中。
構造器加載配置文件方式:
@Test
public void constructor() {//構造函數傳遞多個參數ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml","spring-config2.xml");System.out.println(Arrays.toString(ac.getBeanDefinitionNames()));
}
import引入文件:
<import resource="spring-config2.xml"/>
命名Bean
在IoC容器中,name和id屬性可以為Bean命名,id是全局唯一,name可以有多個。
<bean id="helloSpring" name="helloSpring helloSpring3" class="com.spring.core.HelloSpring"/>
實例化Bean
XML文件,bena標簽他不是對象,只是一個描述創建一個Bean的方式。
IoC容器在幫我們實例化bean對象的方法包括構造器、靜態工廠、實例工廠三種,我們可以指定實例化方式。
(1)構造函數實例化
到多數情況下,IoC容器使用構造器來創建Bean的實例,底層是基于反射的機制,使用無參構造器或者對應的參數的構造器。
內部類的實例化,在XML配置一個類的內部類,只需要在class中指明內部類的全路徑類名。
也可以使用$將內部類名與外部類名分開(如果嵌套超過兩層的內部類,那么就必須使用 $。另外,對于type屬性,則同樣需要使用 $)。
在HelloSpring類創建兩個內部類
public static class StaticInnerClass {public StaticInnerClass() {System.out.println("靜態內部類初始化");}
}public class InnerClass {public InnerClass() {System.out.println("內部類初始化");}
}
XML配置文件配置內部類
<!--靜態內部類的初始化,和外部類一樣的。 使用.或者$將內部類名與外部類名分開都行-->
<bean class="com.spring.core.HelloSpring.StaticInnerClass"/>
<bean class="com.spring.core.HelloSpring.StaticInnerClass"/><bean class="com.spring.core.HelloSpring$StaticInnerClass"/>
<bean class="com.spring.core.HelloSpring$StaticInnerClass"/><!--非靜態內部類的初始化需要依賴外部類對象-->
<bean name="helloSpring " class="com.spring.core.HelloSpring"/><bean class="com.spring.core.HelloSpring.InnerClass"><!--屬性依賴注入,后面會講--><constructor-arg ref="helloSpring"/>
</bean>
<bean class="com.spring.core.HelloSpring$InnerClass"><!--屬性依賴注入,后面會講--><constructor-arg ref="helloSpring"/>
</bean>
(2)靜態工廠方法實例化
工廠模式的應用,我們創建對象讓Spring去調用,通過Spring標簽嵌入。調用一次創建方式,將創建的對象放入容器中去管理。既然是靜態工廠,這個對象不會實例化。
靜態工廠,bean標簽中的class屬性不再是獲取bean,使用factory-bean屬性指定獲取Bean的對象的工廠方法名稱。
靜態方法類
/*** @author lx*/
public class HelloSpringStaticFactory {private static HelloSpring helloSpring = new HelloSpring();/*** 靜態工廠方法** @return 返回HelloSpring實例*/public static HelloSpring getHelloSpring() {System.out.println("靜態工廠方法");return helloSpring;}public HelloSpringStaticFactory() {System.out.println("靜態工廠不會初始化");}
}
XML配置文件配置靜態工廠
<!--class表示靜態工廠的全路徑類名-->
<!--factory-method表示靜態工廠方法-->
<bean name="helloSpring" class="com.spring.core.HelloSpringStaticFactory" factory-method="getHelloSpring"/>
靜態工廠方法違背了Spring的初衷,因為我們還是要編寫代碼new對象,但是適用于那種需要對一個集合進行實例化的情況,因為集合的實例化如果使用配置文件編寫的話,那也挺麻煩的。
(3)實例工廠方法實例化
實例工廠本身要實例化工廠類,隨后從工廠實例的非靜態方法中調用方法獲取所需的bean。
使用factory-bean屬性指定實例工廠的方式的名字(對比靜態工廠是指定方法,實例化工廠指定名字。),factory bean雖然代表一個工廠,但是其實例仍然交給Spring管理,另外Spring中還有一個FactoryBean,這只是一個類不是一個東西。
實例工廠類
/*** @author lx*/
public class HelloSpringInstanceFactory {private static HelloSpring helloSpring = new HelloSpring();/*** 靜態工廠方法** @return 返回HelloSpring實例*/public HelloSpring getHelloSpring() {System.out.println("實例工廠方法");return helloSpring;}public HelloSpringInstanceFactory() {System.out.println("實例工廠會初始化");}
}
XML配置文件
<!--實例化工廠-->
<bean id="helloSpringInstanceFactory" class="com.spring.core.HelloSpringInstanceFactory"/>
<!--factory-bean表示實例工廠的名字-->
<!--factory-method表示實例工廠方法-->
<bean name="helloSpring" factory-bean="helloSpringInstanceFactory" factory-method="getHelloSpring"/>
基于XML的依賴裝配
依賴項注入,是指對象通過僅僅通過構造函數參數,工廠方法的參數或工廠方法構造返回對象實例后,在其上設置屬性來定義其依賴的其他對象。
隨后,在IoC容器創建Bean,會自動注入這個Bean的依賴項。這個過程和之前那種創建Bean,主動通過構造器和setter方法設置依賴項是相反的。(意思就是之前主動注入,和之前那個自己構造器和setter來注入依賴。一個是無感知的被動,一個是要自己主動注入。這就是控制反轉,控制就是誰來注入,反轉就是這個控制轉給別的人。)
IoC就是把Bean交給容器來管理,另一方面有些Bean沒有被配置元信息,但是他卻被別的要管理的Bean依賴,也就是有依賴關系存在。我不想手動去配置這個Bean的配置信息,怎么辦?
這就需要DI,依賴注入來幫助注入依賴項。不再需要主動去查找這些Bean的,直接交給DI來完成。
和前面的IoC的Bean的實例化不同(xml,注解,或者Java代碼 另一方面 構造器,靜態工廠方法,工廠方法類),DI的實例化構造方式只有兩種,一個是構造器另一個是setter方法。
(1)構造器依賴注入
構造器的依賴注入,實際上就是依靠構造函數參數來完成,每一個參數都是一個依賴項,你可以把這個構造器理解成一個靜態工廠方法來實例化依賴項。
Ioc的實例化Bean的構造器代碼
/*** @author lx* 構造器依賴注入*/
public class SimpleConstructorBased {/*** 依賴的兩個屬性*/private String property1;private String property2;/*** 測試構造器依賴注入*/public SimpleConstructorBased(String property1, String property2) {this.property1 = property1;this.property2 = property2;System.out.println("構造器依賴注入");}@Overridepublic String toString() {return "SimpleConstructorBased{" +"property1='" + property1 + '\'' +", property2='" + property2 + '\'' +'}';}
}
XML的配置
<!--構造函數屬性注入-->
<bean id="simpleConstructorBased" class="com.spring.core.SimpleConstructorBased"><!--一個constructor-arg表示一個屬性--><constructor-arg value="v1"/><constructor-arg value="v2"/>
</bean>
要注意要用到子標簽<construct - arg>,一個標簽就是一個依賴注入的依賴項。
指定參數名
想一下有一個這樣的場景,一個是同樣參數個數的構造器而且參數是基本類型的兩個構造器,Spring能不能區分出來呢?
實際上不能,在項目開發中也會發現Spring涉及到的基本類型的都是包裝類,Spring是有一點區分不了基本類型,因為這些基本類型都不是Object的子類,又在中間處理成String,你怎么做反射這些。再加上參數個數又一樣,Spring就會隨便第一個,這個第一個可能不是我們想要的。
這時候我們怎么辦,就需要在在這個子標簽里,設置name屬性來制定實例化用哪個。
<bean id="simpleConstructorBased2"
class="com.spring.core.SimpleConstructorBased2"><constructor-arg value="1" name="property1"/><constructor-arg value="true" name="property3"/>
</bean>
測試Java代碼
/*** @author lx* 構造器依賴注入*/
public class SimpleConstructorBased2 {/*** 依賴的兩個屬性*/private int property1;private String property2;private boolean property3;/*** 測試構造器依賴注入1*/public SimpleConstructorBased2(int property1, String property2) {this.property1 = property1;this.property2 = property2;System.out.println("構造器依賴注入1");}/*** 測試構造器依賴注入2*/public SimpleConstructorBased2(int property1, boolean property3) {this.property1 = property1;this.property3 = property3;System.out.println("構造器依賴注入2");}@Overridepublic String toString() {return "SimpleConstructorBased2{" +"property1=" + property1 +", property2='" + property2 + '\'' +", property3=" + property3 +'}';}
}
指定參數類型
有可能存在這樣一種情況:多個構造器,具有相同的參數名和數量,但是參數類型不一致的情況,這樣的情況下,仍然不能確定到底使用哪一個構造器。
(前面參數數量相同和基本類型,這面是相同數量和相同的參數名)
/*** @author lx* 構造器依賴注入*/
public class SimpleConstructorBasedx {/*** 依賴的四個屬性*/private String property1;private String property2;private int property3;private boolean property4;/*** 測試構造器依賴注入1*/public SimpleConstructorBasedx(String property1, boolean property2) {this.property1 = property1;this.property4 = property2;System.out.println("構造器依賴注入1");}/*** 測試構造器依賴注入2*/public SimpleConstructorBasedx(int property1, boolean property2) {this.property3 = property1;this.property4 = property2;System.out.println("構造器依賴注入2");}/*** 測試構造器依賴注入3*/public SimpleConstructorBasedx(String property1, String property2) {this.property1 = property1;this.property2 = property2;System.out.println("構造器依賴注入3");}/*** 測試構造器依賴注入4*/public SimpleConstructorBasedx(String property1, int property3, String property2) {this.property1 = property1;this.property2 = property2;this.property3 = property3;System.out.println("構造器依賴注入4");}/*** 測試構造器依賴注入5*/public SimpleConstructorBasedx(String property1, String property2, int property3) {this.property1 = property1;this.property2 = property2;this.property3 = property3;System.out.println("構造器依賴注入5");}@Overridepublic String toString() {return "SimpleConstructorBasedx{" +"property1='" + property1 + '\'' +", property2='" + property2 + '\'' +", property3=" + property3 +", property4=" + property4 +'}';}
}
前三個構造器,構造器的形參列表參數名字完全一致,這種情況可以用指定type,
<bean id="simpleConstructorBasedx"class="com.spring.core.SimpleConstructorBasedx"><constructor-arg name="property1" value="1" type="int"/><constructor-arg name="property2" value="true" type="boolean"/>
</bean>
指定參數順序,使用index屬性
<bean id="simpleConstructorBasedx"
class="com.spring.core.SimpleConstructorBasedx"><!--一個constructor-arg表示一個屬性--><constructor-arg name="property1" value="xx" type="java.lang.String"/><constructor-arg name="property3" value="1" type="int" index="1"/><constructor-arg name="property2" value="yy" type="java.lang.String"/>
</bean>
(2)setter參數注入
setter依賴注入通過IoC容器調用參數的setter方法,他執行的順序是在構造器實例化后再實例setter方法來實例化。
ApplicationContext對于他所管理Bean的支持同時基于構造器和基于setter方法的依賴注入,依賴注入的屬性開始都是value的bean字符串保存起來,隨后通過PropertiyEditor(屬性編譯器,Spring內部擴展Java原生的PropertyEditor)轉換為實際類型。轉換過程是IoC容器自動轉換,當然這個編輯器也是可以自定義的。
和前面的標簽對比,setter方法對應的子標簽是,name屬性表示屬性名 value 屬性表示屬性值。
/*** @author lx*/
public class SimpleSetterBased {/*** 依賴的5個屬性*/private String property1;private String property2;private int property3;private boolean property4;private int property5;/*** 構造器依賴注入*/public SimpleSetterBased(String property1, String property2) {this.property1 = property1;this.property2 = property2;System.out.println("構造器依賴注入");}//setter方法依賴注入,idea生成stter方法public void setProperty3(int property3) {System.out.println("setter注入property3");this.property3 = property3;}public void setPr11operty5(int property5) {System.out.println("setter注入property5");this.property5 = property5;}public void setProperty4(boolean property4) {System.out.println("setter注入property4");this.property4 = property4;}@Overridepublic String toString() {return "SimpleSetterBased{" +"property1='" + property1 + '\'' +", property2='" + property2 + '\'' +", property3=" + property3 +", property4=" + property4 +", property5=" + property5 +'}';}
}
xml配置文件
<!--setter and constructor-->
<bean id="simpleSetterBased" class="com.spring.core.SimpleSetterBased"><!--構造器參數 name表示參數名 value 表示參數值--><constructor-arg name="property1" value="xxx"/><constructor-arg name="property2" value="yyy"/><!--setter方法 name表示屬性名 value 表示屬性值--><property name="property3" value="123"/><property name="property4" value="true"/><!--name還可以表示方法名除了set后面的部分,不一定是屬性名--><property name="Pr11operty5" value="321"/>
</bean>
工廠方法的依賴注入(區別之前的依賴注入)
使用靜態工廠方法或者實例工廠方式實例bean時,同樣可以注入依賴,方法上的參數可視為bean的依賴項,用于構造器注入,同樣使用< constructor-arg >標簽,而setter注入則不受影響。
依賴注入的解析流程
容器執行Bean的解析流程
1 ApplicationContext容器被實例化,包含所有Bean的所有配置元信息
2 對于每一個bean,其依賴項以屬性set方法,構造函數參數或靜態工廠方法的參數形式表示。創建bean,這些依賴項傳遞依賴給bean。
3 每個需要注入的依賴項,要實際設置value的值,或對Bean的另一個ref引用,最開始統一為一個字符串格式。
4 屬性值在字符串描述轉換為實際屬性的類型。
循環依賴
bean的依賴項和依賴項的依賴項會在bean創建之前創建(也就是A依賴B,B會在A創建前就創建成功。)
這種依賴關系創建的情況,構造器注入就會出現循環依賴,A依賴B,B依賴A。
一種解決方式就是使用setter注入,當兩個互相依賴的bean都創建完畢之后,才會調用set方法進行依賴注入。
構造器和setter注入的選擇
我們怎么選擇依賴注入的方式,看情況分心:
對于強制依賴使用構造器
對于可選的依賴項選擇setter方法(在setter方法上加@required,使這個屬性為必填屬性)
public class MyBean {private String requiredProperty;@Required // 標記此屬性必須注入public void setRequiredProperty(String requiredProperty) {this.requiredProperty = requiredProperty;}
}
適用于非顯式自動裝配的場景(如早期XML配置),或需明確標記某些屬性為必需依賴的情況。
標記在Setter方法上,表示該屬性必須在Bean初始化時被顯式賦值(通過XML配置、Java Config或自動裝配)
Spring團隊現在推薦使用構造器注入,構造器注入能夠保證注入的組件不可變,并且確保需要的依賴不為null。此外,構造器注入的依賴總是能夠在返回客戶端(組件)代碼的時候保證完全初始化的狀態,還能檢測循環依賴。