IoC:經過容器去控制業務對象之間的依賴關係。控制權由應用代碼中轉到了外部容器,控制權的轉移就是反轉。控制權轉移的意義是下降了類之間的耦合度。java
Spring中將IoC容器管理的對象稱爲Bean,這個和JavaBean並無什麼關係,就跟Java和JavaScript同樣。web
爲了實現IoC功能,Spring提供了兩個類spring
BeanFactory:Bean工廠,藉助於配置文件可以實現對JavaBean的配置和管理,用於向使用者提供Bean的實例。數組
ApplicationContext:ApplicationContext構建在BeanFactory基礎之上,提供了更多的實用功能。緩存
BeanFactory的初始化和ApplicationContext的初始化有一個很大的區別:ApplicationContext初始化時會實例化全部單實例(注意是單實例)的bean,後面調用getBean方法的時候,就能夠直接從緩存中進行讀取;而BeanFactory初始化時不會實例化Bean,直到第一次訪問某個Bean時纔會進行實例化。所以初始化ApplicationContext比BeanFactory慢,但後面調用Bean實例對象的時候則ApplicationContext比BeanFactory快。session
(1)xml配置文件
(2)dom4j解析xml
(3)工廠模式
(4)反射app
(1)基本的jar包 dom
(2)建立Bean,在類裏添加方法ide
public class User { public void add() { System.out.println("666666"); } }
(3)在xml裏配置類函數
在xml文件引入schema約束
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置IoC --> <bean id="user" class="com.codeliu.entity.User"></bean> </beans>
(4)寫代碼測試對象建立
@Test public void testIoC() { // 加載spring配置文件,建立對象 @SuppressWarnings("resource") ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 獲得建立的對象,參數爲配置文件中bean標籤的id User user = (User)context.getBean("user"); System.out.println(user); user.add(); }
(1)使用類的無參數構造函數建立(經常使用)
這種方式記得得有無參構造方法
<!-- 使用類的無參構造函數實例化 --> <bean id="user1" class="com.codeliu.entity.User"></bean>
(2)使用靜態工廠建立
public class StaticFactory { public static User getUser() { return new User(); } }
<!-- 使用靜態工廠方法實例化User --> <bean id="staticFactory" class="com.codeliu.bean.StaticFactory" factory-method="getUser"></bean>
@Test public void testStaticFactory() { // 加載spring配置文件,建立對象 @SuppressWarnings("resource") ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 獲得建立的對象,參數爲配置文件中bean標籤的id User user = (User)context.getBean("staticFactory"); System.out.println(user); }
(3)使用實例工廠建立
public class Factory { public User getUser() { return new User(); } }
<!-- 使用實例工廠實例化User --> <!-- 首先實例化bena3 --> <bean id="factory" class="com.codeliu.bean.Factory"></bean> <bean id="user2" factory-bean="factory" factory-method="getUser"></bean>
@Test public void testFactory() { // 加載spring配置文件,建立對象 @SuppressWarnings("resource") ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 獲得建立的對象,參數爲配置文件中bean標籤的id User user = (User)context.getBean("user2"); System.out.println(user); }
(1)id
惟一,必須以字母開頭,不能包含一些特殊字符。Spring根據class屬性建立對應類的實例後,會以id爲鍵key,實例對象爲值value放入一個Map中,當調用getBean方法時,則會根據id的值從Map中把實例取出來。
(2)class
Bean類的全路徑,不能是接口
(3)name
能夠出現特殊符號,當沒有設置id的時候,name也能夠做爲id
(4)scope
Bean的做用範圍
<bean id="user1" class="com.codeliu.entity.User" scope="singleton"></bean>
進行測試
User user = (User)context.getBean("user1"); User user2 = (User)context.getBean("user1"); // true System.out.println(user == user2);
能夠看到,建立一個實例後,這個惟一實例會被緩存起來,下一次要請求使用就直接返回緩存中的實例。
<bean id="user1" class="com.codeliu.entity.User" scope="prototype"></bean>
進行測試
User user = (User)context.getBean("user1"); User user2 = (User)context.getBean("user1"); // false System.out.println(user == user2);
每次請求都會建立一個新的實例,這樣就會出現頻繁的建立和銷燬對象,形成很大的開銷,所以,若是不是必要,不設置成prototype。
request:web項目中,Spring建立一個Bean的對象,將對象存入request域中
session:web項目中,Spring建立一個Bean的對象,將對象存入session域中
建立對象的時候,向類裏面的屬性設置值。
三種方式實現屬性注入
(1)使用set方法(經常使用)
(2)使用帶參的構造函數
(3)使用接口
spring只支持前兩張方式的注入
(1)使用帶參的構造函數
好比我下面的類
public class PropertyDemo1 { private String name; public PropertyDemo1(String name) { this.name = name; } public void add() { System.out.println("PropertyDemo1" + name); } }
裏面有一個帶參數的構造方法,咱們能夠經過xml配置進行賦值
<!-- 使用帶參的構造方法爲Bean中的屬性設值 --> <bean id="property1" class="com.codeliu.entity.PropertyDemo1"> <!-- name屬性表示Bean類中屬性的名字, value表示要設置的值--> <constructor-arg name="name" value="CodeTiger"></constructor-arg> </bean>
測試一下
@Test public void testProperty1() { // 加載spring配置文件,建立對象 @SuppressWarnings("resource") ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 獲得建立的對象,參數爲配置文件中bean標籤的id PropertyDemo1 p1 = (PropertyDemo1)context.getBean("property1"); p1.add(); }
(2)使用set方法(重點)
咱們定義下面這樣一個類
public class PropertyDemo2 { private String book; public PropertyDemo2() {} public void setBook(String book) { this.book = book; } public void add() { System.out.println(book); } }
有一個屬性book並帶有相應的set方法
<!-- 使用set方法爲Bean中的屬性設值 --> <bean id="property2" class="com.codeliu.entity.PropertyDemo2"> <!-- 使用property標籤 --> <property name="book" value="Think in java"></property> </bean>
進行測試
@Test public void testProperty2() { // 加載spring配置文件,建立對象 @SuppressWarnings("resource") ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 獲得建立的對象,參數爲配置文件中bean標籤的id PropertyDemo2 p2 = (PropertyDemo2)context.getBean("property2"); p2.add(); }
咱們這裏只是使用了一個String類型的屬性,但實際中,咱們應該會用其餘類做爲一個類的屬性,獲取一些更復雜的類型好比Map、List等,這時候該怎麼注入呢?
咱們寫一個service類,一個dao類,而後使用service類去調用dao類的方法,這樣service類中確定會有一個dao類的實例對象,看看這是應該怎麼賦值呢?
public class UserDao { public void add() { System.out.println("dao......."); } }
public class UserService { private UserDao dao; public void setDao(UserDao dao) { this.dao = dao; } public void add() { System.out.println("service....."); dao.add(); } }
在service中,其實仍是和上面set方法賦值同樣的原理,只是配置文件變了,來看看怎麼配置
<!-- 注入對象類型的屬性 --> <!-- 先實例化UserDao --> <bean id="userDao" class="com.codeliu.dao.UserDao"></bean> <bean id="userService" class="com.codeliu.service.UserService"> <!-- name表示 UserService類中屬性的名稱 ref表示配置UserDao的bean標籤的id值--> <property name="dao" ref="userDao"></property> </bean>
只是此次是賦值一個對象,因此咱們得先實例化dao類,才能給service類的dao屬性賦值。注意property 沒有使用value屬性,而是使用了ref屬性。
@Test /** * 經過set方法注入對象類型的屬性 */ public void testProperty3() { // 加載Spring配置文件,建立對象 @SuppressWarnings("resource") ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 獲得建立的對象,參數爲配置文件中bean標籤的id UserService service = (UserService)context.getBean("userService"); service.add(); }
首先在xml文件的最外層bean標籤中加上這麼一句
xmlns:p="http://www.springframework.org/schema/p"
這樣纔可使用p名稱空間。
<bean id="property2" class="com.codeliu.entity.PropertyDemo2" p:book="Think in java"></bean> <!-- 上下兩句等價 --> <bean id="property2" class="com.codeliu.entity.PropertyDemo2"> --> <!-- 使用property標籤 --> <property name="book" value="Think in java"></property> </bean>
上面的配置文件中,上下兩句等價。
那若是我要注入一個對象類型的屬性呢?那就得這麼寫
<bean id="userDao" class="com.codeliu.dao.UserDao"></bean> <bean id="userService" class="com.codeliu.service.UserService"> <!-- name表示 UserService類中屬性的名稱 ref表示配置UserDao的bean標籤的id值--> <property name="dao" ref="userDao"></property> </bean> <!-- 和 上面的等價,注意p:dao-ref,ref必須加上,表示後面的是引用的另外一個bean--> <bean id="userService2" class="com.codeliu.service.UserService" p:dao-ref="userDao"></bean>
注意p後面的,必須帶上-Ref表示是引用另外一個bean
這時候又有問題,若是咱們的屬性名稱叫xxRef,那怎麼辦?寫成
p:daoRef-ref="userDao"
這樣會出錯的。因此p命名空間也不能亂用。
(1)數組類型
(2)List類型
(3)Map類型
(4)java.util.Properties類型
首先在User類中添加上面四種類型的屬性,並添加相應的set方法。直接看配置
<bean id="user1" class="com.codeliu.entity.User"> <!-- 爲數組類型的屬性賦值 --> <property name="arr"> <list> <value>xu</value> <value>liu</value> <value>li</value> <value>guo</value> </list> </property> <!-- 爲List類型的屬性賦值 --> <property name="list"> <list> <value>1</value> <value>2</value> <value>3</value> <value>4</value> </list> </property> <!-- 爲Map類型的屬性賦值 --> <property name="map"> <map> <entry key="1" value="liu"></entry> <entry key="2" value="xu"></entry> <entry key="3" value="guo"></entry> </map> </property> <!-- 爲Properities類型的屬性賦值 --> <property name="properities"> <props> <prop key="1">liu</prop> <prop key="2">xu</prop> </props> </property> </bean>
Spring容許在配置Bean時爲Bean指定繼承和依賴兩種關係。
(1)繼承
有時咱們可能兩個類之間大多數的屬性都相同,這是若是對每一個Bean都重複編寫注入信息就很繁瑣了,這時咱們能夠經過bean標籤的parent屬性重用已有的Bean元素的配置信息。
<!-- Bean之間的繼承。。。不是指類之間的繼承,只是配置信息的複用 --> <bean id="class1" class="com.codeliu.Class1"> <property name="name" value="liu"></property> <property name="age" value="22"></property> <property name="address" value="NJUPT"></property> </bean> <bean id="class2" class="com.codeliu.Class2" parent="class1"> <property name="name" value="xu"></property> </bean>
這裏的繼承指的是配置信息的複用,和傳統的Java類的繼承沒有半毛錢關係。
(2)依賴
IoC能保證在實例化一個Bean時,它所依賴的其餘Bean已經實例化完畢。但有時候咱們有這樣的需求,咱們想要類A比類B先實例化,若是類A是做爲類B的屬性,這還好辦,但關鍵是A不是B的屬性啊,這時咱們能夠經過bean標籤的depends-on屬性進行指定前置依賴的Bean,即便沒有關聯關係。
<!-- 設置兩個Bean的依賴關係,userDao要先於userService實例化 --> <bean id="userDao" class="com.codeliu.dao.UserDao"></bean> <bean id="userService" class="com.codeliu.service.UserService" depends-on="userDao"></bean>
Spring IoC容器能夠自動裝配相互協做Bean之間的關聯關係。能夠經過設置bean標籤的autowire屬性進行設置,該屬性有5種取值分別以下:
(1)no:不使用自動裝配,默認值。必須經過ref進行指定依賴。
(2)byName:根據屬性名自動匹配。若是某個Bean設置了此選項,那麼IoC將根據名字查找與屬性徹底一致的Bean,並將其與屬性自動匹配。如某個Bean設置成byName,該Bean中有一個屬性叫dao(同時要提供set方法),那麼IoC就會查找名爲dao的Bean,用它來裝配dao屬性。
(3)byType:若是容器中存在一個與指定屬性類型相同的Bean,那麼將該屬性自動裝配。若是存在多個該類型的Bean,則拋出異常,並指出不能使用byType方式進行裝配。若是沒有找到相同類型的,則什麼事都不會發生,屬性也不會被設置。若是你但願在沒找到時發出提示信息,能夠設置dependency-check屬性的值爲objects,這樣在找不到的時候就會拋出異常。
(4)constructor:與byType相似,不一樣之處在於它應用於構造器參數,若是屬性在容器中沒有找到與構造器參數類型一致的Bean,則拋出異常。
(5)autodetect:經過Bean的自省機制(introspection)來決定是否使用constructor仍是byType方式進行自動裝配。若是發現默認的構造器,將使用byType方式。
下面摘抄網上的一段話做爲文章的結尾
IOC是一種叫作「控制反轉」的設計思想。
一、較淺的層次——從名字上解析
「控制」就是指對 對象的建立、維護、銷燬等生命週期的控制,這個過程通常是由咱們的程序去主動控制的,如使用new關鍵字去建立一個對象(建立),在使用過程當中保持引用(維護),在失去所有引用後由GC去回收對象(銷燬)。
「反轉」就是指對 對象的建立、維護、銷燬等生命週期的控制由程序控制改成由IOC容器控制,須要某個對象時就直接經過名字去IOC容器中獲取。
二、更深的層次——提到DI,依賴注入,是IOC的一種重要實現
一個對象的建立每每會涉及到其餘對象的建立,好比一個對象A的成員變量持有着另外一個對象B的引用,這就是依賴,A依賴於B。IOC機制既然負責了對象的建立,那麼這個依賴關係也就必須由IOC容器負責起來。負責的方式就是DI——依賴注入,經過將依賴關係寫入配置文件,而後在建立有依賴關係的對象時,由IOC容器注入依賴的對象,如在建立A時,檢查到有依賴關係,IOC容器就把A依賴的對象B建立後注入到A中(組裝,經過反射機制實現),而後把A返回給對象請求者,完成工做。
三、IOC的意義何在? IOC並無實現更多的功能,但它的存在使咱們不須要不少代碼、不須要考慮對象間複雜的耦合關係就能從IOC容器中獲取合適的對象,並且提供了對 對象的可靠的管理,極大地下降了開發的複雜性。