Spring IOC簡述
IOC稱為控制反轉,也有一種說法叫DI(依賴注入)。IOC也是spring最核心的模塊,Spring的所有工作幾乎都圍繞着IOC展開。
什么是控制反轉呢?簡單的說,控制反轉就是把我們要做的事情交給別人來做,就像是招了個小弟專門為我們做事情,我們需要做好的東西時直接去找小弟拿。
這里要做的事情就是new 一個對象。我們不再自己去new對象然后使用,而是spring容器幫我們去創建對象然后我們要用的時候直接去拿就行了。spring幫我們
生成對象就是控制反轉,而我們要用對象從spring取對象就是依賴注入。
一切的開始要從spring容器的加載說起
我認為一切的開端要從spring 容器開始說起,所有工作都是圍繞着Spring容器展開的。
這里只描述兩種最常見的加載spring配置文件的方式,其余方式不做描述。
Spring容器是再spring配置文件被加載的一刻生成的。有兩種常見加載方式,分別為使用BeanFactroy加載和使用AppilicationContext記載。
BeanFactory
BeanFactory加載spring配置文件又可以稱為延遲加載,當BeanFactroy加載完配置文件后,bean並沒有生成,而是當第一次使用bean的使用bean才創建。
Resource resource = new ClassPathResource("applicationContext.xml"); BeanFactory beanFactory = new XmlBeanFactory(resource); Warehouse warehouse = beanFactory.getBean("warehouse",Warehouse.class);
ApplicationContext
而Applicationcontext加載方式是立即加載方式,BeanFactory有的功能它都有,而它有的功能BeanFactroy不一定有,所以是目前最主流的加載方式。
立即加載的意思就是當配置文件加載,spring容器創建的時候,我們配置好的bean就都創建了,這些bean與容器同生共死。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Warehouse warehouse = applicationContext.getBean("warehouse",Warehouse.class);
容器加載完后就得談談bean的創建了
bean有三種創建方式:
默認無參構造函數創建、靜態工廠創建、實例工廠創建
默認無參創建方式
也是最常用的創建方式。我們定義一個實體類時默認有一個無參構造函數,可以不用創建。
//實體類
public class User { private String name; private int age; //get set... }
//bean創建
<bean id="user" class="springIOCTest.User"/>
但是當我們定義的實體類有帶參構造函數時就得顯式的為其創建一個無參構造函數,否則bean創建失敗
public class User { private String name; private int age; public User() { } public User(String name, int age) { this.name = name; this.age = age; } // get set... }
靜態工廠創建bean
首先的先定義一個靜態工廠類
public class StaticFactroy { private User user; public static User getUser() { return new User(); } }
在創建bean的時候類路徑選擇靜態工廠類路徑,指定factory-method
<bean id="user" class="springIOCTest.StaticFactroy" factory-method="getUser"/>
實例工廠創建bean
定義一個普通工廠類
public class InstanceFactory { private User user; public User getUser() { return new User(); } }
在創建bean的時候先創建工廠bean,再根據工廠bean創建我們需要的bean
<bean id="instanceFactroy" class="springIOCTest.InstanceFactory"/> <bean id="user" factory-bean="instanceFactroy" factory-method="getUser"/>
雖然我們知道了如何創建一個bean,但是我們每個bean里面可能有很多屬性,需要我們去注入。
接下來談談bean的注入方式
每當我們new一個對象的時候,經常需要給它里面的屬性設值或者傳遞引用,spring也可以幫我們完成這項工作
比如我們要為上面的User類的name屬性和age屬性注入值。那么有兩種方式注入:屬性注入和構造函數注入
首先做下准備工作,為User類加個實體屬性:
public class User { private String name; private int age; private UserSon mySon; // get set... }
屬性注入
屬性注入就是我們平常new 一個對象后setxxx
<bean id="mySon" class="springIOCTest.UserSon"/> <bean id="user" class="springIOCTest.User"> <property name="name" value="張三"/> <property name="age" value="18"/> <property name="mySon" ref="mySon"/> </bean>
可以看到屬性中包含實體類的時候必須先創建一個該類的bean,然后通過ref的方式注入
構造函數注入
public class User { private String name; private int age; private UserSon mySon; public User(String name, int age, UserSon mySon) { this.name = name; this.age = age; this.mySon = mySon; } // get set... }
<bean id="mySon" class="springIOCTest.UserSon"/> <bean id="user" class="springIOCTest.User"> <constructor-arg index="0" name="name" value="張三"/> <constructor-arg index="1" name="age" value="18"/> <constructor-arg index="2" name="mySon" ref="mySon"/> </bean>
構造函數注入的前提時提供對應的構造函數,但是使用構造函數容易產生一個問題,那就是循環依賴問題
循環依賴問題
當使用構造函數注入的時候可能會出現一下一種情況
public class UserSon { private User father; public UserSon(User father) { this.father = father; } //get set.. }
User實體類需要注入UserSon實例,而UserSon實體類也需要注入User實例,那么在生成bean的時候會出現以下情況
<bean id="mySon" class="springIOCTest.UserSon"> <constructor-arg index="0" name="father" ref="user"/> </bean> <bean id="user" class="springIOCTest.User"> <constructor-arg index="0" name="name" value="張三"/> <constructor-arg index="1" name="age" value="18"/> <constructor-arg index="2" name="mySon" ref="mySon"/> </bean>
這樣就形成了死胡同,類時死鎖,這時候就會報錯。
解決辦法很簡單,改為屬性注入方式即可。
因此,使用構造函數注入的時候可能會產生問題,又比較麻煩,所以實際場景中使用屬性注入是比較常見的
這里要對循環依賴再做個詳細說明
為什么通過setter注入引用可以避免循環依賴而通過構造函數注入就不能呢,這就涉及到了spring容器的三級緩存和bean的提前曝光
舉個例子A引用B,B引用C,C引用A。當Spring容器加載並且要創建BeanA的時候,BeanA根據無參構造函數創建BeanA,但這里BeanA還沒有
成功創建完成,因為它發現了它需要setterB,這時候BeanA雖然沒創建完成但是會把它提前曝光並且加入到三級緩存中,然后容器去加載B,BeanB
根據無參構造函數創建BeanB,BeanB發現需要setterC,先將BeanB提前曝光加入三級緩存,然后容器又去創建BeanC,BeanC根據無參構造函數創建
BeanC,並提前曝光加入三級緩存,然后BeanC從三級緩存中找到BeanA,雖然BeanA還沒創建完成,但是BeanC可以將其setter進去,然后BeanC創建完成
然后BeanB也成功setter進BeanC,BeanA也成功setter進BeanB了。那么為什么構造器相互引用無法避免循環依賴呢,原因是當BeanA在調用構造函數生成BeanA的
時候發現需要BeanB,於是去創建BeanB,由於這時候構造函數沒有執行結束,也就是BeanA無法提前爆光加入三級緩存,BeanB發現它也是通過構造函數注入,且需要注入
BeanA,因此BeanB也無法提前曝光加入三級緩存,因此兩者就無法將對方的引用注入到自己屬性上,產生了死循環。
總而言之
Spring避免循環依賴的方式就是構造函數調用完但是setter還沒執行的半成品Bean提前曝光加入三級緩存,這時候其他需要引用該Bean的就可以從三級緩存中取到這個引用,
雖然Bean還沒創好,但是引用卻是不會隨着后面bean創建完成而該變的。
到這里我們就講完了如何完整的創建一個bean了,接下來就來談談bean的生命周期吧
bean的生命周期
bean的完整生命周期從spring容器着手實例化bean開始,直到最終銷毀bean。其中經過了很多關鍵點,每個關鍵點都涉及到特定的方法調用,
我們可以將這些方法大致分以下四類。
- Bean自身的方法
- Bean級生命周期接口方法
- 容器級生命周期接口方法
- 工廠后處理器接口方法
Bean自身的方法調用Bean的構造函數實例化Bean,調用setter設置Bean的屬性值以及通過<bean> 的 init-method 和destory-method所指定的方法。
Bean級生命周期接口方法如BeanNameAware、BeanFactoryAware、InitialzingBeanh、DisposableBean等等,這些接口方法由Bean類直接實現。
容器級生命周期接口方法獨立於Bean,實現類以容器附加裝置的形式注冊到Spring容器中,一般這些方法產生的影響是整個容器,而不是自己的bean
工廠后處理器接口方法也是容器級別的,在應用上下文裝配配置文件后立即調用。
Bean級生命周期接口方法和容器級生命周期接口方法前者解決Bean個性化處理的問題,后者解決容器中某些Bean共性化處理的問題。
對於Bean生命周期接口的探討。
我們都知道,spring推崇不對應用程序類做任何限制, spring的好處在於用戶可以完全將業務類POJO化,不用實現容器特定的接口,只要關心用戶自定義的接口。
但是如果為了實現這些接口就將Bean和Spring牢牢的綁定在了一起,違反了初衷。
因此我們可以通過<bean> 的 init-method 和destory-method屬性配置方式為Bean指定初始化和銷毀的方法,采用這種方式對bean的生命周期的控制效果和通過Bean級生命周期的
InitialzingBeanh和DisposableBean接口所達到的效果是完全相同的,並且采用前者可以達到了框架解耦的目的。因此除非必要,我們都可以拋棄Bean級生命周期接口方法使用更好的
方式取代。
在這里不對生命周期中的一些spring接口做詳細介紹,若想了解更多,可自行查找資料。
在這里可以簡單的為生命周期做個簡化:
單例模式的bean:
出生:容器加載bean就加載
存活:容器存在bean就存在
死亡:容器銷毀bean就銷毀
原型模式的bean:
出生:bean在使用的時候才創建
存活:bean在使用的期間存活
死亡:當bean不在使用並且沒有其他對象引用該bean的時候由垃圾回收期幫忙回收
現在我們知道bean怎么創建的了,也了解了它的生命周期了,那我們下面就來談談它的作用域吧!
Bean的作用域
Bean有五種作用域:單例,原型,請求,會話,全局會話。
單例(singleton):每個容器中只有一個bean
原型(prototype):用到bean一次生成一個bean
請求(request):一次請求產生一個bean
會話(session):一次會話產生一個bean
全局會話(globalsession):當采用服務器集群的時候同一個session可能訪問不同的服務器,這時候使用該作用域可以做到多個服務器都是同一個bean
配置會話可以在<bean>內的scope中配置
嗯~現在Bean的作用域我們也知道了,但是Bean有三種不同的裝配方式,也就是說我們可以采用三種方式標明該類是一個bean,容器要去加載它。
Bean的裝配方式
- XML方式
- 注解方式
- Java代碼方式
上面我們所用到的例子都是使用xml配置方式聲明一個bean。
注解方式就是在我們要配置的bean類上面加個注釋,一共有四個注釋:
@Component、@Repository、@Service、@Controller;后三個分別對應持久層、服務層、控制層的bean
但是四個注解都是可以統用的,弄了這么多主要是為了提高程序可讀性。使用注解方式還要再spring配置中
加個小小的配置,即掃描加了配置的bean類的所在包
<context:component-scan base-package="test1"/>
由於我主要使用XML配置和注解配置,故Java代碼方式不做展開,但是現在Java代碼配置Bean逐漸取代xml方式,讀者可自行去了解。