Spring之IOC(容器,控制反轉(zhuǎn))
1、IOC(容器)
什么是容器?容器是一種為某種特定組件的運行提供必要支持的一個軟件環(huán)境。例如,Tomcat就是一個Servlet容器,它可以為Servlet的運行提供運行環(huán)境。通常來說,使用容器運行組件,除了提供一個組件運行環(huán)境之外,容器還提供了許多底層服務(wù)。例如,Servlet容器底層實現(xiàn)了TCP連接,解析HTTP協(xié)議等非常復(fù)雜的服務(wù),如果沒有容器來提供這些服務(wù),我們就無法編寫像Servlet這樣代碼簡單,功能強大的組件。
Spring的核心就是提供了一個IoC容器,它可以管理所有輕量級的JavaBean組件,提供的底層服務(wù)包括組件的生命周期管理、配置和組裝服務(wù)、AOP支持,以及建立在AOP基礎(chǔ)上的聲明式事務(wù)服務(wù)等。
1.1、IOC的基本介紹
Spring提供的容器又稱為IoC容器,IoC全稱Inversion of Control,直譯為控制反轉(zhuǎn)。IOC 是面向?qū)ο缶幊讨械囊环N設(shè)計原則,可以用來減低代碼之間的耦合度。在 spring 中,控制反轉(zhuǎn)把對象創(chuàng)建和對象之間的調(diào)用過程,交給 spring 進行管理,減低了代碼的耦合度。
誰控制誰,控制什么:傳統(tǒng)Java SE程序設(shè)計,我們直接在對象內(nèi)部通過new進行創(chuàng)建對象,是程序主動去創(chuàng)建依賴對象;而IoC是有專門一個容器來創(chuàng)建這些對象,即由Ioc容器來控制對象的創(chuàng)建;誰控制誰?當(dāng)然是IoC 容器控制了對象;控制什么?那就是主要控制了外部資源獲取(不只是對象包括比如文件等)。
為何是反轉(zhuǎn),哪些方面反轉(zhuǎn)了:有反轉(zhuǎn)就有正轉(zhuǎn),傳統(tǒng)應(yīng)用程序是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉(zhuǎn);而反轉(zhuǎn)則是由容器來幫忙創(chuàng)建及注入依賴對象;為何是反轉(zhuǎn)?因為由容器幫我們查找及注入依賴對象,對象只是被動的接受依賴對象,所以是反轉(zhuǎn);哪些方面反轉(zhuǎn)了?依賴對象的獲取被反轉(zhuǎn)了。
在IoC模式下,控制權(quán)發(fā)生了反轉(zhuǎn),即從應(yīng)用程序轉(zhuǎn)移到了IoC容器,所有組件不再由應(yīng)用程序自己創(chuàng)建和配置,而是由IoC容器負責(zé),這樣,應(yīng)用程序只需要直接使用已經(jīng)創(chuàng)建好并且配置好的組件。
用圖例說明一下,傳統(tǒng)程序設(shè)計如圖2-1,都是主動去創(chuàng)建相關(guān)對象然后再組合起來:

當(dāng)有了IoC/DI的容器后,在客戶端類中不再主動去創(chuàng)建這些對象了,如圖2-2所示:

IoC不是一種技術(shù),只是一種思想,一個重要的面向?qū)ο缶幊痰姆▌t,它能指導(dǎo)我們?nèi)绾卧O(shè)計出松耦合、更優(yōu)良的程序。傳統(tǒng)應(yīng)用程序都是由我們在類內(nèi)部主動創(chuàng)建依賴對象,從而導(dǎo)致類與類之間高耦合,難于測試;有了IoC容器后,把創(chuàng)建和查找依賴對象的控制權(quán)交給了容器,由容器進行注入組合對象,所以對象與對象之間是松散耦合,這樣也方便測試,利于功能復(fù)用,更重要的是使得程序的整個體系結(jié)構(gòu)變得非常靈活。
參考:http://www.rzrgm.cn/NancyStartOnce/p/6813162.html
1.2、IOC的基本使用
下載完 sprig 解壓文件后可以看到很多jar 包,將 spring 所必需的四個包和 commons-logging 包導(dǎo)入項目當(dāng)中:

spring 框架依賴的jar包有 commons-logging,如果不添加的話會報錯。
先編寫一個簡單的 User 類,然后在 src 目錄下新建一個spring的配置文件即 xml 文件,該配置文件可自定義,比如 bean01.xml。

User 類:
package test;
public class User {
public void add() {
System.out.println("add。。。");
}
}
spring 配置文件 bean01.xml 如下,class 里面寫的是完整類名。
<?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">
<!-- 配置創(chuàng)建的對象-->
<bean id="user" class="test.User"></bean>
</beans>
然后就可以隨便建一個測試類來進行測試:
package test.testDemo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import test.User;
public class Test01 {
public static void main(String[] args) {
//加載spring配置文件,并創(chuàng)建了配置文件中配置的類的實例對象)
ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml");
//獲取bean,即配置創(chuàng)建的對象
User user = (User) context.getBean("user"); //getBean()方法里面的參數(shù)是 xml 配置文件中的bean節(jié)點的id
//調(diào)用
user.add();
}
}
執(zhí)行上面代碼可以看到輸出 User 類中的 add() 方法。
默認情況下,通過 bean 獲取到的實例對象是單例的,即多次通過 getBean() 方法獲取同一個 bean 時(在不同配置文件中,即使 bean 標(biāo)簽的 class 指向的是同一個類,也不是同一個bean,此時創(chuàng)建的不是同一個實例對象),獲取到的實例對象實際上都是同一個對象。
1.3、IOC底層原理
原理:通過解析 xml 配置文件獲取類名,然后通過反射來創(chuàng)建該類的一個實例對象并返回。

如果后面類發(fā)生了改變,比如類名改了,只需修改配置文件即可,所以大大降低了耦合度。(因為如果是傳統(tǒng)程序,類名發(fā)生修改,在所有引用到該類的地方都需要進行修改)
2、Spring 中Bean相關(guān)基本介紹
在 Spring 框架中,Bean 是由 Spring IoC(Inversion of Control,控制反轉(zhuǎn))容器管理的對象。Spring 容器負責(zé)創(chuàng)建、初始化、配置和銷毀這些 Bean。一個 Java 類只要被 Spring 容器管理,就可以被稱為 Spring Bean。
(只要是一個普通的 Java 類,Spring 就可以對其進行管理,并不要求該類嚴(yán)格遵循 JavaBean 規(guī)范。)
2.1、spring對于bean的操作
bean 管理指的是兩個操作:
- spring 創(chuàng)建對象
- spring 注入屬性
IoC又稱為依賴注入(DI:Dependency Injection),依賴注入就是注入屬性,注入屬性是在創(chuàng)建對象的基礎(chǔ)之上完成的,先創(chuàng)建對象,實際上是然后再注入屬性。
bean 實現(xiàn)上述兩個操作有兩種方式:
- 基于xml配置文件方式實現(xiàn)
- 基于注解方式實現(xiàn)
Spring 的 IoC 容器同時支持屬性注入和構(gòu)造方法注入,并允許混合使用。
在設(shè)計上,Spring的IoC容器是一個高度可擴展的無侵入容器。所謂無侵入,是指應(yīng)用程序的組件無需實現(xiàn)Spring的特定接口,或者說,組件根本不知道自己在Spring的容器中運行。這種無侵入的設(shè)計有以下好處:
- 應(yīng)用程序組件既可以在Spring的IoC容器中運行,也可以自己編寫代碼自行組裝配置;
- 測試的時候并不依賴Spring容器,可單獨進行測試,大大提高了開發(fā)效率。
2.2、FactoryBean(工廠bean)
在普通的 bean 當(dāng)中,class屬性指的是什么類,則取到的就是該類的實例對象。但通過 FactoryBean 我們可以自定義 bean 的創(chuàng)建過程,包括自定義返回的類、是否單例、bean的類型。
定義一個工廠 bean,MyBean.java如下,可以看到,實際返回的是 User bean:
package test; import org.springframework.beans.factory.FactoryBean; public class Mybean implements FactoryBean { @Override public User getObject() throws Exception { //實際返回的對象實例 User user = new User(); return user; } @Override public Class<?> getObjectType() { //Bean的類型 return null; } @Override public boolean isSingleton() { //是否為單例,true為單例,false為非單例 return false; } }
配置文件:
<?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"> <bean id="mybean" class="test.Mybean"></bean> </beans>
測試代碼:
定義FactoryBean后,Spring創(chuàng)建的Bean實際上是這個FactoryBean的getObject()方法返回的Bean。
package test.testDemo; import dao.UserDao; import dao.impl.UserDaoImpl; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.UserService; import service.impl.UserServiceImpl; import test.Mybean; import test.User; import test.User02; public class Test01 { public static void main(String[] args) { //加載spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean04.xml"); //獲取配置創(chuàng)建的對象,實際將返回User類實例。這里的 getBean() 方法要同時寫上 id 和類.class,否則可能報錯 User user = context.getBean("mybean", User.class); user.setName("wen"); System.out.println(user); } }
2.3、bean的作用域(作用范圍)
bean 的作用域可以理解為 bean 的作用范圍。Spring3 中為 Bean 定義了5種作用域,分別為 singleton(單例,默認值)、prototype、request、session 和 global session。

2.3.1、singleton作用域(單例,默認值)
singleton:單例模式,Singleton 作用域是Spring 中的默認作用域。在單例模式下,多次通過 getBean() 方法獲取同一個 bean 時,獲取到的實例對象實際上都是同一個對象。在不同配置文件中,即使 bean 標(biāo)簽的 class 指向的是同一個類,也不是同一個bean,此時創(chuàng)建的不是同一個實例對象。在同一個配置文件中,指向的是不同的class,也不會是同一個實例對象。
Singleton 是單例類型,在加載配置文件時即創(chuàng)建 bean 對象,不管你是否使用,他都存在了,每次獲取到的對象都是同一個對象。
該模式在多線程下是不安全的。Singleton 作用域是Spring 中的缺省作用域,也可以顯示的將 Bean 定義為 singleton 模式,配置為:
<bean id="..." class="..." scope="singleton"></bean>
2.3.1、prototype作用域
prototype:原型模式,多實例。在每次通過 Spring 容器獲取 prototype 定義的 bean 時,容器都會創(chuàng)建一個新的 Bean 實例,每個 Bean 實例都有自己的屬性和狀態(tài)。跟單例 singleton 不同, singleton 全局只有一個對象。
<bean id="SingletonBean" class="com.spring.demo.SingletonBean" scope="prototype"></bean>
2.4、bean的生命周期
bean 的生命周期,即 bean 從創(chuàng)建到銷毀的過程。
bean 的生命周期如下:
- 實例化。通過構(gòu)造器創(chuàng)建 bean 實例
- 屬性賦值。設(shè)置 bean 的屬性(依賴注入)
- 調(diào)用后置處理器的 postProcessBeforeInitialization() 方法(需在 xml 中配置后置處理器,跟配置一個bean一樣,class指向后置處理器即可。后置處理器實際上就是一個實現(xiàn)了BeanPostProcessor接口的類)
- 初始化。調(diào)用 bean 標(biāo)簽 init-method 指定的類的方法,init-method 指定的方法由類定義,可進行一些初始化操作(需手動配置)。初始化完成后 bean 就可以使用了
- 調(diào)用后置處理器的 postProcessAfterInitialization() 方法
- 銷毀。當(dāng)容器關(guān)閉時,調(diào)用 bean 標(biāo)簽 destroy-method 指定的類的方法,destroy-method 指定的方法由類定義。(可調(diào)用ApplicationContextObj.close()方法來手動銷毀bean)
3、IOC創(chuàng)建對象
3.1、基于XML配置文件創(chuàng)建對象
在spring配置文件中,使用 bean 標(biāo)簽就可以實現(xiàn)對象的創(chuàng)建。如下:
<bean id="user" class="test.User"></bean>
基于 xml 配置文件創(chuàng)建對象時,默認情況下是執(zhí)行該類的無參數(shù)構(gòu)造函數(shù)來創(chuàng)建對象的,所以如果該類沒有無參構(gòu)造函數(shù)程序?qū)箦e。
bean 標(biāo)簽常見屬性:
- id:給該類指定一個唯一標(biāo)識。每個
<bean ...>都有一個id標(biāo)識,相當(dāng)于Bean的唯一ID。 - class:類的完整類名
3.2、注解方式創(chuàng)建對象(@Component、@Service、@Controller、@Repository)
使用Spring的IoC容器,實際上就是通過類似XML這樣的配置文件,把我們自己的Bean的依賴關(guān)系描述出來,然后讓容器來創(chuàng)建并裝配Bean。一旦容器初始化完畢,我們就直接從容器中獲取Bean使用它們。使用XML配置的優(yōu)點是所有的Bean都能一目了然地列出來,并通過配置注入能直觀地看到每個Bean的依賴。它的缺點是寫起來非常繁瑣,每增加一個組件,就必須把新的Bean配置到XML中。我們可以使用注解的方式進行配置,讓Spring自動掃描Bean并組裝它們。
spring 針對 bean 管理中創(chuàng)建對象提供了四種注解:
- @Component
- @Service 業(yè)務(wù)層
- @Controller web層
- @Repository 持久層
創(chuàng)建對象有四種注解,但目前來說,這四個注解的功能都是一樣的。只是建議 @Service 用在業(yè)務(wù)層,@Controller 用在 web 層,@Repository 用在持久層,但并不是強制的,可以混用。有四個注解只是為了后續(xù)的版本當(dāng)中進行功能擴展 。
使用注解創(chuàng)建對象:
先導(dǎo)入依賴包,除了 spring 的幾個基本包外,還需要導(dǎo)入 aop 包:

然后需要開啟組件掃描。開啟組件掃描需要先引入命名空間,然后就可以使用 <context:component-scan base-package="要掃描的包路徑"></context:component-scan> 標(biāo)簽開啟組件掃描:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--開啟組件掃描。base-package寫包名,若要掃描多個包,可以用逗號隔開,或者直接寫多個包共用的上級目錄--> <context:component-scan base-package="test, service.impl"></context:component-scan> </beans>
開啟組件掃描后就可以給類添加注解了,下面的@Component可以換成 @Service等其它的幾個注解,效果都一樣。
package test; import org.springframework.stereotype.Component; //注解里面value屬性名及值都可以省略不寫,即寫成@Compnent,此時bean的id默認是類名首字母小寫后的名稱 @Component(value = "user") //相當(dāng)于<bean id="user" class="完整類名"></bean> public class User { public void add() { System.out.println("add..."); } }
驗證,使用bean:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import test.User; public class Test { public static void main(String[] args) { //加載spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); //獲取配置創(chuàng)建的對象 User user = (User) context.getBean("user"); System.out.println(user); user.add(); } }
3.2.1、組件掃描配置
開啟組件掃描后,默認情況是配置的包下的所有類、所有注解都會掃描。我們可以配置只掃描哪些注解,或者不掃描哪些注解。
配置只掃描 @Controller 注解,注意,要加上user-default-filters標(biāo)簽:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="test, service" use-default-filters="false"> <!--use-default-filters表示不使用默認filter--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> </beans>
配置了只掃描 @Controller 注解后,其他注解將不會被掃描到,如果使用其他注解的 bean 程序?qū)箦e:No bean named 'xxx' available...
配置不掃描@Controller 注解,注意,不能加上user-default-filters標(biāo)簽:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="test, service"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> </beans>
3.2.2、注解配置bean作用域(@Scope())
對于Spring容器來說,當(dāng)我們把一個Bean標(biāo)記為添加注解比如@Component后,它就會自動為我們創(chuàng)建一個單例(Singleton),即容器初始化時創(chuàng)建Bean,容器關(guān)閉前銷毀Bean。在容器運行期間,我們調(diào)用getBean(Class)獲取到的Bean總是同一個實例。
我們可以通過 @Scope() 注解來配置 bean 的作用域:
比如聲明為 prototype 作用域:
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype") public class MailSession { ... }
4、IOC依賴注入(注入屬性)
4.1、基于XML配置文件的依賴注入
我們可以在 xml 文件中直接配置在創(chuàng)建該類的對象時,同時給該類配置屬性。
基于 xml 配置文件來注入類屬性有兩種方式:
- 通過類的 set() 方法注入屬性。類似 setName() 等等
- 通過類的有參構(gòu)造函數(shù)注入屬性
4.1.1、通過set()方法注入屬性(property標(biāo)簽)
我們在類中定義了 setter,就可以在 spring 的配置文件中通過配置直接給該類實例指定屬性值。代碼示例如下:
User類:
public class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
配置文件:
<?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"> <!-- 配置創(chuàng)建的對象--> <bean id="user" class="springtest.User"> <property name="name" value="wen"></property> <property name="age" value="12"></property> </bean> </beans>
使用上述配置后,在創(chuàng)建 User 類時會自動給該類注入屬性。
測試代碼:
package test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import springtest.User; public class test01 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); //獲取配置創(chuàng)建的對象 User user = (User) context.getBean("user"); System.out.println(user.getName() + user.geAge()); //輸出 wen12 } }
4.1.2、通過有參構(gòu)造函數(shù)注入屬性(constructor-arg標(biāo)簽)
如果一個類中有有參構(gòu)造函數(shù),我們就可以通過有參構(gòu)造來給該類的實例注入屬性。當(dāng)類中有有參構(gòu)造函數(shù),我們也只能使用有參構(gòu)造來注入屬性,否則將會報錯。
代碼示例如下:
User 類:
package springtest; public class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //有參構(gòu)造。定義了有參構(gòu)造則編譯器不會自動創(chuàng)建無參構(gòu)造,所以配置文件中如果不使用有參構(gòu)造來注入屬性的話程序會報錯 public User(String name, int age) { this.name = name; this.age = age; } }
配置文件:
<?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"> <bean id="user" class="springtest.User"> <!-- 也可以使用索引的形式 <constructor-arg index="0" value="wen"></constructor-arg> --> <constructor-arg name="name" value="wen"></constructor-arg> <constructor-arg name="age" value="11"></constructor-arg> </bean> </beans>
使用上述配置后,在創(chuàng)建 User 類時會自動調(diào)用該類的有參構(gòu)造來給該類注入屬性。
測試代碼:
package test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import springtest.User; public class test01 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); //獲取配置創(chuàng)建的對象 User user = (User) context.getBean("user"); System.out.println(user.getName() + user.geAge()); //輸出 wen11 } }
4.1.3、注入外部bean(注入類)
如果注入的屬性值是boolean、int、String這樣的數(shù)據(jù)類型,可以通過value注入。如果注入的屬性值是類,則可以通過 ref 注入。
示例:
UserServiceImpl 類中有一個 setter 需要設(shè)置 userDao 屬性為 UserDao 類:
public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add() { this.userDao.add(); } }
在傳統(tǒng)程序中,需要調(diào)用 setUserDao() 方法來主動將一個 UserDao 類注入。寫法如下:
public class Test01 { public static void main(String[] args) { UserServiceImpl userServiceImpl = new UserServiceImpl(); UserDao userDao = new UserDaoImpl(); userServiceImpl.setUserDao(userDao); //調(diào)用 setUserDao 注入UserDao類 userServiceImpl.add(); } }
使用spring配置文件可以直接將 UserDao bean作為屬性注入 UserService當(dāng)中,實際上相當(dāng)于調(diào)用了 setUserDao() 方法。配置文件如下:
<?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"> <bean name="userdao" class="dao.impl.UserDaoImpl"></bean> <bean name="userservice" class="service.impl.UserServiceImpl"> <property name="userDao" ref="userdao"></property> </bean> </beans>
Bean的順序不重要,Spring根據(jù)依賴關(guān)系會自動正確初始化。
測試代碼:
package test.testDemo; import dao.UserDao; import dao.impl.UserDaoImpl; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.UserService; import service.impl.UserServiceImpl; import test.User; public class Test01 { public static void main(String[] args) { //加載spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean02.xml"); UserServiceImpl userServiceImpl = context.getBean(UserServiceImpl.class); userServiceImpl.add(); } }
4.1.4、內(nèi)部bean注入類
上面注入外部bean,實際上就是在外部建一個bean,然后將該bean賦值給 UserService bean的屬性。除了上面的寫法,我們還可以采用內(nèi)部bean的寫法來將一個類注入給另一個類的屬性。
<?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"> <bean name="userservice" class="service.impl.UserServiceImpl"> <property name="userDao"> <bean id="userdao" class="dao.impl.UserDaoImpl"></bean> <!-- 如果該內(nèi)部bean還需要注入屬性,可以再給該內(nèi)部bean配置property --> </property> </bean> </beans>
4.1.5、注入集合
給類注入集合,示例如下:
package test; import java.util.*; public class User02 { private String[] myArr; private List<String> myList; private Set<String> mySet; private Map<String, String> myMap;public void setMyArr(String[] myArr) { this.myArr = myArr; } public void setMyList(List<String> myList) { this.myList = myList; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } }
配置文件:
<?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"> <bean id="user02" class="test.User02"> <!-- 給數(shù)組注入數(shù)據(jù) --> <property name="myArr"> <array> <value>AAA</value> <value>BBB</value> </array> </property>
<!-- 注入 list 集合數(shù)據(jù) --> <property name="myList"> <list> <value>AAA</value> <value>BBB</value> </list> </property>
<!-- 注入 set 集合數(shù)據(jù) --> <property name="mySet"> <set> <value>AAA</value> <value>BBB</value> </set> </property>
<!-- 注入 Map 數(shù)據(jù) --> <property name="myMap"> <map> <entry key="testA" value="aaa"></entry> <entry key="testB"> <value>bbb</value> </entry> </map> </property> </bean> </beans>
上面的集合當(dāng)中的元素的值都是字符串,如果集合當(dāng)中的元素是類的話,我們可以使用 ref 標(biāo)簽來注入:
<?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"> <bean id="user" class="test.User02"> <!-- myList2集合的元素是User類實例對象 --> <property name="myList2"> <list> <ref bean="user01"></ref> <ref bean="user02"></ref> </list> </property> </bean> <bean id="user01" class="test.User"></bean> <bean id="user02" class="test.User"></bean> </beans>
上面將集合注入屬性只是注入了某個類當(dāng)中,我們可以把集合提取出來,多個類就可以重用這些集合。
首先我們需要先導(dǎo)入 util 命名空間,util命名空間提供了集合相關(guān)的配置,在使用命名空間前要導(dǎo)入util命名空間,如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd"> </beans>
下面提取出一個 List 并在 bean 中注入,代碼示例:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd"> <util:list id="userList"> <value>張三</value> <value>李四</value> <value>王五</value> </util:list> <bean id="user02" class="test.User02"> <!-- 注入 list 集合數(shù)據(jù) --> <property name="myList" ref="userList"></property> </bean> </beans>
4.1.6、注入其它類型值(空值、特殊符號)
注入空值:
<bean id="user" class="test.User"> <property name="name"> <null></null> </property> <property name="age" value="12"></property> </bean>
注入特殊符號:
XML中共有5個特殊的字符,分別是:&<> “’。如果配置文件中的注入值包括這些特殊字符,就需要進行特別處理。
有兩種解決方法:
- 采用本例中的<![CDATA[ ]]>特殊標(biāo)簽,將包含特殊字符的字符串封裝起來。<![CDATA[ ]]>的作用是讓XML解析器將標(biāo)簽中的字符串當(dāng)作普通的文本對待,以防止某些字符串對XML格式造成破壞。
- 使用XML轉(zhuǎn)義序列表示這些特殊的字符,這5個特殊字符所對應(yīng)XML轉(zhuǎn)義序列如下:

示例:給 name 屬性賦值為 <<wen>>
<bean id="user" class="test.User"> <property name="name" value="<<wen>>"></property> <property name="age" value="12"></property> </bean>
<bean id="user" class="test.User"> <property name="name"> <value><![CDATA[<<wen>>]]></value> <!-- 給name賦值為<<wen>> --> </property> <property name="age" value="12"></property> </bean>
4.1.7、自動裝配(autowire)
傳統(tǒng)的XML方式配置 Bean 組件都是通過 <property> 標(biāo)簽為Bean的屬性注入所需的值,當(dāng)需要維護的Bean組件及需要注入的屬性更多時,勢必會增加配置的工作量,這時我們可以使用自動裝配。
使用自動裝配只需給 bean 標(biāo)簽添加 autowire 屬性即可。配置示例:
<bean id="user" class="test.User" autowire="byName"/>
通過設(shè)置<bean>元素的autowire屬性指定自動裝配,代替了通過<property>標(biāo)簽顯示指定Bean的依賴關(guān)系。由BeanFactory檢查XML配置文件的內(nèi)容,為Bean自動注入依賴關(guān)系。
Spring提供了多種自動裝配方式,autowire屬性常用的取值如下所示
- no:不使用自動裝配。Bean依賴關(guān)系必須通過property元素定義。
- byName:根據(jù)屬性名自動裝配。BeanFactory查找容器中的全部Bean,找出 id 與屬性的 setter 方法入?yún)⑵ヅ涞腂ean。找到即自動注入,否則什么都不做。
- byType:根據(jù)屬性類型自動裝配。BeanFactory查找容器中的全部Bean,如果正好有一個與依賴屬性類型相同的Bean,就自動裝配這個屬性;但是如果有多個這樣的Bean,Spring無法決定注入哪個Bean,就拋出一個致命異常;如果沒有匹配的Bean,就什么都不會發(fā)生,屬性不會被設(shè)置。
- constructor:與byType的方式類似,不同之處在于它應(yīng)用于構(gòu)造器參數(shù)。如果在容器中沒有找到與構(gòu)造器參數(shù)類型一致的Bean,那么將會拋出異常
自動裝配的局限性:
- 不是所有類型都可以使用自動裝配,不能自動裝配的數(shù)據(jù)類型有:Object、基本數(shù)據(jù)類型(Date、CharSequence、Number、URI、URL、Class、String)等等。因為自動裝配是注入了一個bean。
- 自動裝配不如顯式裝配精確,如果可能的話盡量使用顯式裝配。
實例:
假設(shè) User 類中有一個屬性類型為 Animal 類:
public class User { private Animal animal; public Animal getAnimal() { return animal; } public void setAnimal(Animal animal) { this.animal = animal; } }
此時我們可以使用自動裝配:
<?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"> <bean id="user" class="test.User" autowire="byName"></bean> <bean id="animal" class="test.Animal"></bean> </beans>
上面使用 byName 類型的自動裝配,id 為animal 的 bean 將匹配到 User 類中的 setter,所以會將 animal 類注入 User 中的 animal 屬性當(dāng)中。(上面配置直接改成byType也可以)
驗證代碼:
package UnitDemo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import test.User; import test.User02; public class Test { public static void main(String[] args) { ApplicationContext context2 = new ClassPathXmlApplicationContext("bean01.xml"); User user = (User) context2.getBean("user"); user.getAnimal().setName("cat"); System.out.println(user.getAnimal().getName()); } }
4.1.8、引入properties配置文件
如果配置過多,在一個 xml 文件里面維護可能相對比較困難,這時我們可以在一個 propreties 配置文件中配置一些屬性,然后再在 xml 配置文件中引入 propreties 文件的配置。比如可用于連接數(shù)據(jù)庫的配置。
示例:
在項目的 src 目錄下建一個 properties 類型文件 jdbc.properties,properties 類型文件的內(nèi)容是鍵值對形式。往該文件寫入以下內(nèi)容:
prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/userDB prop.username=root prop.password=123456
然后就可以在 xml 配置文件中引入該文件,在引入之前我們需要先寫 context 命名空間:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>
最后就可以通過 <context> 標(biāo)簽將 properties 文件引入,并且可以用 ${} 來使用 properties 文件的值:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--引入外部屬性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <!--通過${}使用外部配置文件的值--> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.username}"></property> <property name="password" value="${prop.password}"></property> </bean> </beans>
4.2、注解方式依賴注入(@Autowired、@Qualifier、@Resource、@Value)
spring 注解注入屬性提供了幾種注解:
- @Autowired:根據(jù)屬性類型進行自動裝配,即 byType
- @Qualifier:根據(jù)屬性名稱進行自動裝配,即 byName。需要配合@Autowired進行使用,用于在@Autowired匹配到多個類時,指定究竟使用哪個類來進行注入。
- @Resource:既可以根據(jù)類型注入,也可以根據(jù)名稱注入。不指定 name 和 type 則自動按照 byName 方式進行裝配,如果沒有匹配成功,則回退為一個原始類型進行匹配,如果匹配成功則自動裝配。
- @Value:注入基本數(shù)據(jù)類型數(shù)據(jù)
使用注解,比如@Autowired,就相當(dāng)于把指定類型的Bean注入到指定的字段中。和XML配置相比,注解的方式大幅簡化了注入,因為它不但可以寫在set()方法上,還可以直接寫在字段上,甚至可以寫在構(gòu)造方法中。
@Component public class UserService { MailService mailService; public UserService(@Autowired MailService mailService) { this.mailService = mailService; } ... }
4.2.1、@Autowired(根據(jù)類型裝配)
@Autowired 注解會根據(jù)屬性類型進行自動裝配,即 byType。默認情況下它要求依賴對象必須存在,如果不存在,程序?qū)箦e。但我們也可以設(shè)置它的required屬性為false,來讓它的依賴對象如果允許為 null 值,@Autowired(required = false)
實例:比如在 UserServiceImpl 類中的某個屬性注入 UserDaoImpl 類型值。
UserDaoImpl 代碼:只需給實現(xiàn)類添加注解即可,接口不能添加注解
package dao.impl; import dao.UserDao; import org.springframework.stereotype.Repository; @Repository public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("userdaoimpl add..."); } }
UserServiceImpl 代碼:
package service.impl; import dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import service.UserService; @Service public class UserServiceImpl implements UserService { //不需要添加set方法 @Autowired private UserDao userDao; @Override public void add() { System.out.println("userserviceimpl add..."); userDao.add(); } }
驗證代碼:
package UnitDemo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.UserService; import test.User; public class Test { public static void main(String[] args) { //加載spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); //獲取配置創(chuàng)建的對象 UserService userService = (UserService) context.getBean("userServiceImpl"); userService.add(); //將輸出 userserviceimpl add... userdaoimpl add... } }
4.2.2、@Qualifier(通過名稱標(biāo)識唯一類注入@Autowired中)
使用 @Autowired 時,如果某個類有多個實現(xiàn)類,則 spring 無法識別究竟將哪個實現(xiàn)類來進行注入,此時程序?qū)苯訄箦e。此時我們可以將 @Autowired 和 @Qualifier 搭配使用,@Qualifier 可通過名稱來標(biāo)識究竟使用哪個類來進行注入。
@Qualifier 用法示例:
假設(shè) UserDao 有多個實現(xiàn)類:UserDaoImpl、UserDaoImpl02,UserDaoImpl02代碼如下:
package dao.impl; import dao.UserDao; import org.springframework.stereotype.Repository; @Repository public class UserDaoImpl02 implements UserDao { @Override public void add() { System.out.println("userdaoimpl02 add..."); } }
此時如果我們直接使用 @Autowired 程序?qū)苯訄箦e,因為 spring 無法識別究竟使用哪個類來進行注入。所以我們可以使用 @Qualifier("bean標(biāo)識") 來指定究竟使用哪個類來進行注入:
package service.impl; import dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import service.UserService; @Service public class UserServiceImpl implements UserService { @Autowired @Qualifier("userDaoImpl02") private UserDao userDao; @Override public void add() { System.out.println("userserviceimpl add..."); userDao.add(); } }
驗證代碼:
package UnitDemo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.UserService; import test.User; public class Test { public static void main(String[] args) { //加載spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); //獲取配置創(chuàng)建的對象 UserService userService = (UserService) context.getBean("userServiceImpl"); userService.add(); //將輸出 userserviceimpl add... userdaoimpl02 add... } }
4.2.3、@Resource(byType、byName)
匹配規(guī)則:
- 如果指定了name,則從上下文中查找名稱(id)匹配的bean進行裝配,找不到則拋出異常。
- 如果指定了type,則從上下文中找到類似匹配的唯一bean進行裝配,找不到或是找到多個,都會拋出異常。
- 如果同時指定了name和type,則從Spring上下文中找到唯一匹配的bean進行裝配,找不到則拋出異常。
- 如果既沒有指定name,又沒有指定type,則自動按照byName方式進行裝配;如果沒有匹配,則回退為一個原始類型進行匹配,如果匹配則自動裝配。
UserDaoImpl02 實現(xiàn)類:
package dao.impl; import dao.UserDao; import org.springframework.stereotype.Repository; @Repository public class UserDaoImpl02 implements UserDao { @Override public void add() { System.out.println("userdaoimpl02 add..."); } }
UserServiceImpl 實現(xiàn)類:
package service.impl; import dao.UserDao; import org.springframework.stereotype.Service; import service.UserService; import javax.annotation.Resource; @Service public class UserServiceImpl implements UserService { @Resource(name = "userDaoImpl02") private UserDao userDao; @Override public void add() { System.out.println("userserviceimpl add..."); userDao.add(); } }
驗證代碼:
package UnitDemo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.UserService; import test.User; public class Test { public static void main(String[] args) { //加載spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); //獲取配置創(chuàng)建的對象 UserService userService = (UserService) context.getBean("userServiceImpl"); userService.add(); //將輸出 userserviceimpl add... userdaoimpl02 add... } }
4.2.4、@Value(注入基本數(shù)據(jù)類型)
@Value 注解即注入一個基本類型數(shù)據(jù)。
import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component(value = "user") public class User { @Value(value = "wen") private String name; public void showName() { System.out.println(this.name); } }
驗證代碼:
package UnitDemo; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import service.UserService; import test.User; public class Test { public static void main(String[] args) { //加載spring配置文件 ApplicationContext context = new ClassPathXmlApplicationContext("bean01.xml"); //獲取配置創(chuàng)建的對象 User user = (User) context.getBean("user"); user.showName(); //輸出 wen } }
4.2.5、注入list
有些時候,我們會有一系列接口相同,不同實現(xiàn)類的Bean。例如,注冊用戶時,我們要對email、password和name這3個變量進行驗證。為了便于擴展,我們先定義驗證接口:
public interface Validator { void validate(String email, String password, String name); }
然后,分別使用3個Validator對用戶參數(shù)進行驗證:
@Component public class EmailValidator implements Validator { public void validate(String email, String password, String name) { if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) { throw new IllegalArgumentException("invalid email: " + email); } } } @Component public class PasswordValidator implements Validator { public void validate(String email, String password, String name) { if (!password.matches("^.{6,20}$")) { throw new IllegalArgumentException("invalid password"); } } } @Component public class NameValidator implements Validator { public void validate(String email, String password, String name) { if (name == null || name.isBlank() || name.length() > 20) { throw new IllegalArgumentException("invalid name: " + name); } } }
最后,我們通過一個Validators作為入口進行驗證:
@Component public class Validators { @Autowired List<Validator> validators; public void validate(String email, String password, String name) { for (var validator : this.validators) { validator.validate(email, password, name); } } }
注意到Validators被注入了一個List<Validator>,Spring會自動把所有類型為Validator的Bean裝配為一個List注入進來,這樣一來,我們每新增一個Validator類型,就自動被Spring裝配到Validators中了,非常方便。
因為Spring是通過掃描classpath獲取到所有的Bean,而List是有序的,要指定List中Bean的順序,可以加上@Order注解:
@Component @Order(1) public class EmailValidator implements Validator { ... } @Component @Order(2) public class PasswordValidator implements Validator { ... } @Component @Order(3) public class NameValidator implements Validator { ... }
4.2.5、初始化和銷毀
有些時候,一個Bean在注入必要的依賴后,需要進行初始化(監(jiān)聽消息等)。在容器關(guān)閉時,有時候還需要清理資源(關(guān)閉連接池等)。我們通常會定義一個init()方法進行初始化,定義一個shutdown()方法進行清理,然后,引入JSR-250定義的Annotation:
在Bean的初始化和清理方法上標(biāo)記@PostConstruct和@PreDestroy:
@Component public class MailService { @Autowired(required = false) ZoneId zoneId = ZoneId.systemDefault(); @PostConstruct public void init() { System.out.println("Init mail service with zoneId = " + this.zoneId); } @PreDestroy public void shutdown() { System.out.println("Shutdown mail service"); } }
Spring容器會對上述Bean做如下初始化流程:
- 調(diào)用構(gòu)造方法創(chuàng)建
MailService實例; - 根據(jù)
@Autowired進行注入; - 調(diào)用標(biāo)記有
@PostConstruct的init()方法進行初始化。
而銷毀時,容器會首先調(diào)用標(biāo)記有@PreDestroy的shutdown()方法。Spring只會根據(jù)注解查找無參數(shù)方法,對方法名不作要求。
4.2.6、完全注解開發(fā)(@Configuration)
我們可以通過 spring 配置類來實現(xiàn)完全注解開發(fā),即不需要 xml 配置文件。
用 @Configuration 注解來新建配置類,比如命名為 SpringConfig,以此替代 xml 配置文件:
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = {"test", "dao", "service"}) //指定 spring 在初始化容器時要掃描的包。作用和在 spring 的 xml 配置文件中的<context:component-scan base-package=“org.woster”/>是一樣的。 public class SpringConfig { }
使用@ComponentScan來告訴容器需要掃描的包,如果不指定參數(shù),即只有@ComponentScan,則會自動搜索當(dāng)前配置類所在的包以及子包。
使用配置類獲取 ApplicationContext 對象的語法跟使用配置文件的不太一樣,除此之外,其他情況都一樣:
package UnitDemo; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import test.SpringConfig; import test.User; public class Test { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); User user = (User) context.getBean("user"); user.showName(); } }

浙公網(wǎng)安備 33010602011771號