Spring 學習目錄
前言
深入理解DIP、IoC、DI以及IoC容器:https://www.cnblogs.com/liuhaorain/p/3747470.html
轉載自:[Spring框架入門教程 (biancheng.net)](http://c.biancheng.net/spring/bean-autowiring.html)
為什么要使用IOC
傳統開發模式的弊端
三層架構是經典的開發模式,我們一般將視圖控制、業務邏輯和數據庫操作分別抽離出來單獨形成一個類,這樣各個職責就非常清晰且易於復用和維護。大致代碼如下:
用戶DAO
public class UserDAO {
private String database;
public UserDAO(String dataBase) {
this.database = dataBase;
}
public void doSomething() {
System.out.println("保存用戶!");
}
}
用戶Service
public class UserService {
private UserDAO dao;
public UserService(UserDAO dao) {
this.dao = dao;
}
public void doSomething() {
dao.doSomething();
}
}
用戶Controller
public class Controller {
public UserService service;
public Controller(UserService userService) {
this.service = userService;
}
public void doSomething() {
service.doSomething();
}
}
接下來我們就必須手動一個一個創建對象,並將dao、service、controller依次組裝起來,然后才能調用。
public static void main(String[] args) {
UserDAO dao = new UserDAO("mysql");
UserService service = new UserService(dao);
Controller controller = new Controller(service);
controller.doSomething();
}
分析一下這種做法的弊端有哪些呢?
- 在生成Controller的地方我們都必須先創建dao再創建service最后再創建Controller,這么一條繁瑣的創建過程。
- 在這三層結構當中,上層都需要知道下層是如何創建的,上層必須自己創建下層,這樣就形成了緊密耦合。為什么業務程序員在寫業務的時候卻需要知道數據庫的密碼並自己創建dao呢?不僅如此,當如果dao的數據庫密碼變化了,在每一處生成Controller的地方都需要進行修改。
- 通過new關鍵字生成了具體的對象,這是一種硬編碼的方式,違反了面向接口編程的原則。當有一天我們從Hibernate更換到Mybatis的時候,在每一處new DAO的地方,我們都需要進行更換。
- 我們頻繁的創建對象,浪費了資源。
這時候我們再來看看如果用SpringIOC的情況,剛才的代碼變成如下。
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
Controller controller = (Controller) context.getBean("controller");
controller.doSomething();
}
很明顯,使用IOC之后,我們只管向容器索取所需的Bean即可。IOC便解決了以下的痛點:
- Bean之間的解耦,這種解耦體現在我們沒有在代碼中去硬編碼bean之間的依賴。(不通過new操作依次構建對象,由springIOC內部幫助我們實現了依賴注入)。一方面,IOC容器將通過new對象設置依賴的方式轉變為運行期動態的進行設置依賴。
- IOC容器天然地給我們提供了單例。
- 當需要更換dao的時候,我們只需要在配置文件中更換dao的實現類,完全不會破壞到之前的代碼。
- 上層現在不需要知道下層是如何創建的。
Spring IoC容器
IoC容器是Spring的核心,也可以稱為Spring容器。Spring 通過 IoC 容器來管理對象的實例化和初始化,以及對象從創建到銷毀的整個生命周期。
Spring 中使用的對象都由 IoC 容器管理,不需要我們手動使用 new 運算符創建對象。由 IoC 容器管理的對象稱為 Spring Bean,Spring Bean 就是 Java 對象,和使用 new 運算符創建的對象沒有區別。
Spring 通過讀取 XML 或 Java 注解中的信息來獲取哪些對象需要實例化。
Spring 提供 2 種不同類型的 IoC 容器,即 BeanFactory 和 ApplicationContext 容器。
BeanFactory 容器
BeanFactory 是最簡單的容器,由 org.springframework.beans.factory.BeanFactory 接口定義,采用懶加載(lazy-load),所以容器啟動比較快。BeanFactory 提供了容器最基本的功能。
為了能夠兼容 Spring 集成的第三方框架(如 BeanFactoryAware、InitializingBean、DisposableBean),所以目前仍然保留了該接口。
簡單來說,BeanFactory 就是一個管理 Bean 的工廠,它主要負責初始化各種 Bean,並調用它們的生命周期方法。
BeanFactory 接口有多個實現類,最常見的是 org.springframework.beans.factory.xml.XmlBeanFactory。使用 BeanFactory 需要創建 XmlBeanFactory 類的實例,通過 XmlBeanFactory 類的構造函數來傳遞 Resource 對象。如下所示。
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory factory = new XmlBeanFactory(resource);
ApplicationContext 容器
ApplicationContext 繼承了 BeanFactory 接口,由 org.springframework.context.ApplicationContext 接口定義,對象在啟動容器時加載。ApplicationContext 在 BeanFactory 的基礎上增加了很多企業級功能,例如 AOP、國際化、事件支持等。
ApplicationContext 接口有兩個常用的實現類,具體如下。
ClassPathXmlApplicationContext
該類從類路徑 ClassPath 中尋找指定的 XML 配置文件,並完成 ApplicationContext 的實例化工作,具體如下所示。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation);
在上述代碼中,configLocation 參數用於指定 Spring 配置文件的名稱和位置,如 Beans.xml。
FileSystemXmlApplicationContext
該類從指定的文件系統路徑中尋找指定的 XML 配置文件,並完成 ApplicationContext 的實例化工作,具體如下所示。
ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
它與 ClassPathXmlApplicationContext 的區別是:在讀取 Spring 的配置文件時,FileSystemXmlApplicationContext 不會從類路徑中讀取配置文件,而是通過參數指定配置文件的位置。即 FileSystemXmlApplicationContext 可以獲取類路徑之外的資源,如“F:/workspaces/Beans.xml”。
通常在 Java 項目中,會采用 ClassPathXmlApplicationContext 類實例化 ApplicationContext 容器的方式,而在 Web 項目中,ApplicationContext 容器的實例化工作會交由 Web 服務器完成。Web 服務器實例化 ApplicationContext 容器通常使用基於 ContextLoaderListener 實現的方式,它只需要在 web.xml 中添加如下代碼:
<!--指定Spring配置文件的位置,有多個配置文件時,以逗號分隔-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--spring將加載spring目錄下的applicationContext.xml文件-->
<param-value>
classpath:spring/applicationContext.xml
</param-value>
</context-param>
<!--指定以ContextLoaderListener方式啟動Spring容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
需要注意的是,BeanFactory 和 ApplicationContext 都是通過 XML 配置文件加載 Bean 的。
二者的主要區別在於,如果 Bean 的某一個屬性沒有注入,使用 BeanFacotry 加載后,第一次調用 getBean() 方法時會拋出異常,而 ApplicationContext 則會在初始化時自檢,這樣有利於檢查所依賴的屬性是否注入。
因此,在實際開發中,通常都選擇使用 ApplicationContext,只有在系統資源較少時,才考慮使用 BeanFactory。
Spring Bean定義
由 Spring IoC 容器管理的對象稱為 Bean,Bean 根據 Spring 配置文件中的信息創建。
可以把 Spring IoC 容器看作是一個大工廠,Bean 相當於工廠的產品,如果希望這個大工廠生產和管理 Bean,則需要告訴容器需要哪些 Bean,以及需要哪種方式裝配 Bean。
Spring 配置文件支持兩種格式,即 XML 文件格式和 Properties 文件格式。
- Properties 配置文件主要以 key-value 鍵值對的形式存在,只能賦值,不能進行其他操作,適用於簡單的屬性配置。
- XML 配置文件是樹形結構,相對於 Properties 文件來說更加靈活。XML 配置文件結構清晰,但是內容比較繁瑣,適用於大型復雜的項目。
通常情況下,Spring 的配置文件使用 XML 格式。XML 配置文件的根元素是 <beans>,該元素包含了多個子元素 <bean>。每一個 <bean> 元素都定義了一個 Bean,並描述了該 Bean 如何被裝配到 Spring 容器中。
在上一節中的 Beans.xml 配置文件,代碼如下所示:
<?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-3.0.xsd">
<bean id="helloWorld" class="com.springlearn.HelloWorld">
<property name="message" value="Hello World!" />
</bean>
</beans>
解釋
-
xmlns="http://www.springframework.org/schema/beans"
聲明xml文件默認的命名空間,表示未使用其他命名空間的所有標簽的默認命名空間。
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
聲明XML Schema實例,聲明后就可以使用schemaLocation屬性。
-
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd“指定Schema的位置這個屬性必須結合命名空間使用。這個屬性有兩個值,第一個值表示需要使用的命名空間。第二個值表示供命名空間使用的XML schema的位置。
上面配置的命名空間指定xsd規范文件,這樣你在進行下面具體配置的時候就會根據這些xsd規范文件給出相應的提示,比如說每個標簽是怎么寫的,都有些什么屬性是都可以智能提示的,在啟動服務的時候也會根據xsd規范對配置進行校驗。
配置技巧:對於屬性值的寫法是有規律的,中間使用空格隔開,后面的值是前面的補充,也就是說,前面的值是去除了xsd文件后得來的。
上述代碼中,使用 id 屬性定義了 Bean,並使用 class 屬性指定了 Bean 對應的類。
<bean> 元素中可以包含很多屬性,其常用屬性如下表所示。
| 屬性名稱 | 描述 |
|---|---|
| id | Bean 的唯一標識符,Spring 容器對 Bean 的配置和管理都通過該屬性完成。id 的值必須以字母開始,可以使用字母、數字、下划線等符號。 |
| name | name 屬性中可以為 Bean 指定多個名稱,每個名稱之間用逗號或分號隔開。Spring 容器可以通過 name 屬性配置和管理容器中的 Bean。 |
| class | 該屬性指定了 Bean 的具體實現類,它必須是一個完整的類名,即類的全限定名。 |
| scope | 用於設定 Bean 實例的作用域,屬性值可以為 singleton(單例)、prototype(原型)、request、session 和 global Session。其默認值是 singleton |
| constructor-arg |
|
| property |
|
| ref |
|
| value |
|
| list | 用於封裝 List 或數組類型的依賴注入 |
| set | 用於封裝 Set 類型的依賴注入 |
| map | 用於封裝 Map 類型的依賴注入 |
| entry | |
| init-method | 容器加載 Bean 時調用該方法,類似於 Servlet 中的 init() 方法 |
| destroy-method | 容器刪除 Bean 時調用該方法,類似於 Servlet 中的 destroy() 方法。該方法只在 scope=singleton 時有效 |
| lazy-init | 懶加載,值為 true,容器在首次請求時才會創建 Bean 實例;值為 false,容器在啟動時創建 Bean 實例。該方法只在 scope=singleton 時有效 |
Spring Bean作用域
Bean的生命周期,從創建到銷毀。
在配置文件中,除了可以定義 Bean 的屬性值和相互之間的依賴關系,還可以聲明 Bean 的作用域。例如,如果每次獲取 Bean 時,都需要一個 Bean 實例,那么應該將 Bean 的 scope 屬性定義為 prototype,如果 Spring 需要每次都返回一個相同的 Bean 實例,則應將 Bean 的 scope 屬性定義為 singleton。
作用域的種類
Spring 容器在初始化一個 Bean 實例時,同時會指定該實例的作用域。Spring 5 支持以下 6 種作用域。
-
singleton
默認值,單例模式,表示在 Spring 容器中只有一個 Bean 實例,Bean 以單例的方式存在。
-
prototype
原型模式,表示每次通過 Spring 容器獲取 Bean 時,容器都會創建一個 Bean 實例。
-
request
每次 HTTP 請求,容器都會創建一個 Bean 實例。該作用域只在當前 HTTP Request 內有效。
-
session
同一個 HTTP Session 共享一個 Bean 實例,不同的 Session 使用不同的 Bean 實例。該作用域僅在當前 HTTP Session 內有效。
-
application
同一個 Web 應用共享一個 Bean 實例,該作用域在當前 ServletContext 內有效。
類似於 singleton,不同的是,singleton 表示每個 IoC 容器中僅有一個 Bean 實例,而同一個 Web 應用中可能會有多個 IoC 容器,但一個 Web 應用只會有一個 ServletContext,也可以說 application 才是 Web 應用中貨真價實的單例模式。
-
websocket
websocket 的作用域是 WebSocket ,即在整個 WebSocket 中有效。
注意:Spring 5 版本之前還支持 global Session,該值表示在一個全局的 HTTP Session 中,容器會返回該 Bean 的同一個實例。一般用於 Portlet 應用環境。Spring 5.2.0 版本中已經將該值移除了。
request、session、application、websocket 和 global Session 作用域只能在 Web 環境下使用,如果使用 ClassPathXmlApplicationContext 加載這些作用域中的任意一個的 Bean,就會拋出以下異常。
java.lang.IllegalStateException: No Scope registered for scope name 'xxx'
下面我們詳細講解常用的兩個作用域:singleton 和 prototype。
singleton
singleton 是 Spring 容器默認的作用域。
當 Bean 的作用域為 singleton 時,Spring 容器中只會存在一個共享的 Bean 實例。該 Bean 實例將存儲在高速緩存中,並且所有對 Bean 的請求,只要 id 與該 Bean 定義相匹配,都會返回該緩存對象。
通常情況下,這種單例模式對於無會話狀態的 Bean(如 DAO 層、Service 層)來說,是最理想的選擇。
在 Spring 配置文件中,可以使用 <bean> 元素的 scope 屬性,將 Bean 的作用域定義成 singleton,其配置方式如下所示:
<bean id="..." class="..." scope="singleton"/>
例子
我們重新改裝MainApp類
package com.springlearn;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.setMessage("對象A");
objA.getMessage();
HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
objB.getMessage();
}
}
由於Spring 容器默認的作用域是singleton。
所以Bean.xml可以不用修改,當前你也可以加上scope
<?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-3.0.xsd">
<bean id="helloWorld" class="com.springlearn.HelloWorld" scope="singleton" />
</beans>
運行結果如下
message : 對象A
message : 對象A
從運行結果可以看出,兩次輸出內容相同,這說明 Spring 容器只創建了一個 HelloWorld 類的實例。由於 Spring 容器默認作用域是 singleton,所以如果省略 scope 屬性,其輸出結果也會是一個實例。
prototype
瞬時
對於 prototype 作用域的 Bean,Spring 容器會在每次請求該 Bean 時都創建一個新的 Bean 實例。prototype 作用域適用於需要保持會話狀態的 Bean(如 Struts2 的 Action 類)。
在 Spring 配置文件中,可以使用
<bean id="..." class="..." scope="prototype"/>
例子
修改配置文件 Beans.xml,內容如下。
<?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-3.0.xsd">
<bean id="helloWorld" class="com.springlearn.HelloWorld" scope="prototype" />
</beans>
運行結果如下
message : 對象A
message : null
從運行結果可以看出,兩次輸出的內容並不相同,這說明在 prototype 作用域下,Spring 容器創建了兩個不同的 HelloWorld 實例。
Spring Bean生命周期
在傳統的 Java 應用中,Bean 的生命周期很簡單,使用關鍵字 new 實例化 Bean,當不需要該 Bean 時,由 Java 自動進行垃圾回收。
Spring 中 Bean 的生命周期為:Bean 的定義 -> Bean 的初始化 -> Bean 的使用 -> Bean 的銷毀。
Spring 根據 Bean 的作用域來選擇管理方式。對於 singleton 作用域的 Bean,Spring 能夠精確地知道該 Bean 何時被創建,何時初始化完成,以及何時被銷毀;而對於 prototype 作用域的 Bean,Spring 只負責創建,當容器創建了 Bean 的實例后,Bean 的實例就交給客戶端代碼管理,Spring 容器將不再跟蹤其生命周期。
Spring Bean生命周期執行流程
Spring 容器在確保一個 Bean 能夠使用之前,會進行很多工作。Spring 容器中 Bean 的生命周期流程如下圖所示。

Bean 生命周期的整個執行過程描述如下。
- Spring 啟動,查找並加載需要被 Spring 管理的 Bean,並實例化 Bean。
- 利用依賴注入完成 Bean 中所有屬性值的配置注入。
- 如果 Bean 實現了 BeanNameAware 接口,則 Spring 調用 Bean 的 setBeanName() 方法傳入當前 Bean 的 id 值。
- 如果 Bean 實現了 BeanFactoryAware 接口,則 Spring 調用 setBeanFactory() 方法傳入當前工廠實例的引用。
- 如果 Bean 實現了 ApplicationContextAware 接口,則 Spring 調用 setApplicationContext() 方法傳入當前 ApplicationContext 實例的引用。
- 如果 Bean 實現了 BeanPostProcessor 接口,則 Spring 調用該接口的預初始化方法 postProcessBeforeInitialzation() 對 Bean 進行加工操作,此處非常重要,Spring 的 AOP 就是利用它實現的。
- 如果 Bean 實現了 InitializingBean 接口,則 Spring 將調用 afterPropertiesSet() 方法。
- 如果在配置文件中通過 init-method 屬性指定了初始化方法,則調用該初始化方法。
- 如果 BeanPostProcessor 和 Bean 關聯,則 Spring 將調用該接口的初始化方法 postProcessAfterInitialization()。此時,Bean 已經可以被應用系統使用了。
- 如果在
中指定了該 Bean 的作用域為 singleton,則將該 Bean 放入 Spring IoC 的緩存池中,觸發 Spring 對該 Bean 的生命周期管理;如果在 中指定了該 Bean 的作用域為 prototype,則將該 Bean 交給調用者,調用者管理該 Bean 的生命周期,Spring 不再管理該 Bean。 - 如果 Bean 實現了 DisposableBean 接口,則 Spring 會調用 destory() 方法銷毀 Bean;如果在配置文件中通過 destory-method 屬性指定了 Bean 的銷毀方法,則 Spring 將調用該方法對 Bean 進行銷毀。
Spring 為 Bean 提供了細致全面的生命周期過程,實現特定的接口或設置
了解 Spring 生命周期的意義就在於,可以利用 Bean 在其存活期間的指定時刻完成一些相關操作。一般情況下,會在 Bean 被初始化后和被銷毀前執行一些相關操作。
Spring 官方提供了 3 種方法實現初始化回調和銷毀回調:
- 實現 InitializingBean 和 DisposableBean 接口;
- 在 XML 中配置 init-method 和 destory-method;
- 使用 @PostConstruct 和 @PreDestory 注解。
在一個 Bean 中有多種生命周期回調方法時,優先級為:注解 > 接口 > XML。
不建議使用接口和注解,這會讓 pojo 類和 Spring 框架緊耦合。
初始化回調
-
使用接口
修改
HelloWorld類package com.springlearn; import org.springframework.beans.factory.InitializingBean; public class HelloWorld implements InitializingBean { private String message; public void setMessage(String message) { this.message = message; } public void getMessage() { System.out.println("message : " + message); } @Override public void afterPropertiesSet() throws Exception { System.out.printf("調用接口:InitializingBean,方法:afterPropertiesSet,無參數"); } }調用接口:InitializingBean,方法:afterPropertiesSet,無參數message : 對象A 調用接口:InitializingBean,方法:afterPropertiesSet,無參數message : null -
配置XML
bean.xml
<?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-3.0.xsd"> <bean id="helloWorld" class="com.springlearn.HelloWorld" init-method="init" scope="prototype" /> </beans>添加
init()方法package com.springlearn; import org.springframework.beans.factory.InitializingBean; public class HelloWorld{ private String message; public void setMessage(String message) { this.message = message; } public void getMessage() { System.out.println("message : " + message); } public void init() { System.out.println("調用init-method指定的初始化方法:init" ); } } -
使用注解
使用 @PostConstruct 注解標明該方法為 Bean 初始化后的方法。
public class HelloWrold { @PostConstruct public void init() { System.out.println("@PostConstruct注解指定的初始化方法:init" ); } }使用
@PostConstruct注解需要引入javax.annotation在
pom.xml中添加<dependency> <groupId>javax.annotation</groupId> <artifactId>jsr250-api</artifactId> <version>1.0</version> </dependency>
銷毀回調
-
使用接口
org.springframework.beans.factory.DisposableBean 接口提供了以下方法:
void destroy() throws Exception;您可以實現以上接口,在 destroy 方法內指定 Bean 初始化后需要執行的操作。
<bean id="..." class="..." /> public class HelloWrold implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("調用接口:InitializingBean,方法:afterPropertiesSet,無參數"); } } -
配置XML
<bean id="..." class="..." destroy-method="destroy"/> public class HelloWrold { public void destroy() { System.out.println("調用destroy-method指定的銷毀方法:destroy" ); } } -
使用注解
使用 @PreDestory 注解標明該方法為 Bean 銷毀前執行的方法。
public class HelloWrold { @PreDestory public void init() { System.out.println("@PreDestory注解指定的初始化方法:destroy" ); } }
示例
HelloWorld
package com.springlearn;
public class HelloWorld{
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("message : " + message);
}
public void init() {
System.out.println("Bean正在進行初始化");
}
public void destroy() {
System.out.println("Bean將要被銷毀");
}
}
Bean.xml
<?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-3.0.xsd">
<bean id="helloWorld" class="com.springlearn.HelloWorld" init-method="init" destroy-method="destroy">
<property name="message" value="Hello World!" />
</bean>
</beans>
MainApp
package com.springlearn;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Thread.currentThread();
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.getMessage();
//銷毀容器
context.registerShutdownHook();
}
}
Bean正在進行初始化
message : Hello World!
Bean將要被銷毀
默認的初始化和銷毀方法
如果多個 Bean 需要使用相同的初始化或者銷毀方法,不用為每個 bean 聲明初始化和銷毀方法,可以使用 default-init-method 和 default-destroy-method 屬性,如下所示。
<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-3.0.xsd"
default-init-method="init"
default-destroy-method="destroy">
<bean id="..." class="...">
...
</bean>
</beans>
BeanPostProcessor(Spring后置處理器)
BeanPostProcessor 接口也被稱為后置處理器,通過該接口可以自定義調用初始化前后執行的操作方法。
BeanPostProcessor 接口源碼如下:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
postProcessBeforeInitialization 在 Bean 實例化、依賴注入后,初始化前調用。postProcessAfterInitialization 在 Bean 實例化、依賴注入、初始化都完成后調用。
當需要添加多個后置處理器實現類時,默認情況下 Spring 容器會根據后置處理器的定義順序來依次調用。也可以通過實現 Ordered 接口的 getOrder 方法指定后置處理器的執行順序。該方法返回值為整數,默認值為 0,值越大優先級越低。
新建InitHelloWorld 類代碼如下
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
public class InitHelloWorld implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("A Before : " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("A After : " + beanName);
return bean;
}
@Override
public int getOrder() {
return 5;
}
}
需要注意的是,postProcessBeforeInitialization 和 postProcessAfterInitialization 方法返回值不能為 null,否則會報空指針異常或者通過 getBean() 方法獲取不到 Bean 實例對象,因為后置處理器從Spring IoC 容器中取出 Bean 實例對象后沒有再次放回到 IoC 容器中。
InitHelloWorld2 的代碼如下。
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
public class InitHelloWorld2 implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("B Before : " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("B After : " + beanName);
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
beans.xml
<?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-3.0.xsd">
<bean id="helloWorld" class="net.biancheng.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="message" value="Hello World!" />
</bean>
<!-- 注冊處理器 -->
<bean class="net.biancheng.InitHelloWorld" />
<bean class="net.biancheng.InitHelloWorld2" />
</beans>
MainApp
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
context.registerShutdownHook();
}
}
B Before : helloWorld
A Before : helloWorld
Bean正在初始化
B After : helloWorld
A After : helloWorld
Message : Hello World!
Bean將要被銷毀
由運行結果可以看出,postProcessBeforeInitialization 方法是在 Bean 實例化和依賴注入后,自定義初始化方法前執行的。而 postProcessAfterInitialization 方法是在自定義初始化方法后執行的。由於 getOrder 方法返回值越大,優先級越低,所以 InitHelloWorld2 先執行。
Spring Bean繼承
Bean 定義可以包含很多配置信息,包括構造函數參數、屬性值和容器的一些具體信息,如初始化方法、銷毀方法等。子 Bean 可以繼承父 Bean 的配置數據,根據需要,子 Bean 可以重寫值或添加其它值。
需要注意的是,Spring Bean 定義的繼承與 Java 中的繼承無關。您可以將父 Bean 的定義作為一個模板,其它子 Bean 從父 Bean 中繼承所需的配置。
在配置文件中通過 parent 屬性來指定繼承的父 Bean。
package com.springlearn;
public class HelloWorld {
private String message1;
private String message2;
public void setMessage1(String message) {
this.message1 = message;
}
public void setMessage2(String message) {
this.message2 = message;
}
public void getMessage1() {
System.out.println("World Message1 : " + message1);
}
public void getMessage2() {
System.out.println("World Message2 : " + message2);
}
}
package com.springlearn;
public class HelloWorld2 {
private String message1;
private String message2;
private String message3;
public void setMessage1(String message) {
this.message1 = message;
}
public void setMessage2(String message) {
this.message2 = message;
}
public void setMessage3(String message) {
this.message3 = message;
}
public void getMessage1() {
System.out.println("HelloWorld2 Message1 : " + message1);
}
public void getMessage2() {
System.out.println("HelloWorld2 Message2 : " + message2);
}
public void getMessage3() {
System.out.println("HelloWorld2 Message3 : " + message3);
}
}
在配置文件中,分別為 HelloWorld 中的 message1 和 message2 賦值。使用 parent 屬性將 HelloWorld2 定義為 HelloWorld 的子類,並為 HelloChain 中的 message1 和 message3 賦值。Beans.xml 文件代碼如下。
<?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-3.0.xsd">
<bean id="helloWorld" class="com.springlearn.HelloWorld">
<property name="message1" value="Hello World! Parent" />
<property name="message2" value="Hello World2!Parent" />
</bean>
<bean id="helloWorld2" class="com.springlearn.HelloWorld2" parent="helloWorld">
<property name="message1" value="Hello World! Child" />
<property name="message3" value="Hello World3!Child" />
</bean>
</beans>
package com.springlearn;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.getMessage1();
objA.getMessage2();
HelloWorld2 objB =(HelloWorld2) context.getBean("helloWorld2");
objB.getMessage1();
objB.getMessage2();
objB.getMessage3();
//銷毀容器
context.registerShutdownHook();
}
}
輸出結果如下
World Message1 : Hello World! Parent
World Message2 : Hello World2!Parent
HelloWorld2 Message1 : Hello World! Child
HelloWorld2 Message2 : Hello World2!Parent
HelloWorld2 Message3 : Hello World3!Child
由結果可以看出,我們在創建 helloWorld2 時並沒有給 message2 賦值,但是由於 Bean 的繼承,將值傳遞給了 message2。
定義模板
您可以創建一個 Bean 定義模板,該模板只能被繼承,不能被實例化。創建 Bean 定義模板時,不用指定 class 屬性,而是指定 abstarct="true" 將該 Bean 定義為抽象 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-3.0.xsd">
<bean id="beanTeamplate" class="com.springlearn.HelloWorld" abstract="true">
<property name="message1" value="Hello World! Parent" />
<property name="message2" value="Hello World2!Parent" />
</bean>
<bean id="helloWorld2" class="com.springlearn.HelloWorld2" parent="beanTeamplate">
<property name="message1" value="Hello World! Child" />
<property name="message3" value="Hello World3!Child" />
</bean>
</beans>
Spring依賴注入
Spring 依賴注入(Dependency Injection,DI)和控制反轉含義相同,它們是從兩個角度描述的同一個概念。使用依賴注入可以更輕松的管理和測試應用程序。
當某個 Java 實例需要另一個 Java 實例時,傳統的方法是由調用者創建被調用者的實例(例如,使用 new 關鍵字獲得被調用者實例),而使用 Spring 框架后,被調用者的實例不再由調用者創建,而是由 Spring 容器創建,這稱為控制反轉。
Spring 容器在創建被調用者的實例時,會自動將調用者需要的對象實例注入給調用者,調用者通過 Spring 容器獲得被調用者實例,這稱為依賴注入。
依賴注入主要有兩種實現方式,分別是 setter 注入(又稱設值注入)和構造函數注入。具體介紹如下。
-
構造函數注入
指 IoC 容器使用構造函數注入被依賴的實例。可以通過調用帶參數的構造函數實現依賴注入,每個參數代表一個依賴。
-
setter 注入
指 IoC 容器使用 setter 方法注入被依賴的實例。通過調用無參構造器或無參 static 工廠方法實例化 Bean 后,調用該 Bean 的 setter 方法,即可實現基於 setter 的 DI。
在 Spring 實例化 Bean 的過程中,首先會調用默認的構造方法實例化 Bean 對象,然后通過 Java 的反射機制調用 setXxx() 方法進行屬性的注入。因此,setter 注入要求 Bean 的對應類必須滿足以下兩點要求。
- 必須提供一個默認的無參構造方法。
- 必須為需要注入的屬性提供對應的 setter 方法。
使用 setter 注入時,在 Spring 配置文件中,需要使用 <bean> 元素的子元素 <property> 為每個屬性注入值。而使用構造注入時,在配置文件中,主要使用 <constructor-arg> 標簽定義構造方法的參數,使用其 value 屬性(或子元素)設置該參數的值。
構造函數注入
下面使用 <constructor-arg> 標簽實現構造函數注入。
在 <constructor-arg> 標簽中,包含 ref、value、type、index 等屬性。value 屬性用於注入基本數據類型以及字符串類型的值;ref 屬性用於注入已經定義好的 Bean;type 屬性用來指定對應的構造函數,當構造函數有多個參數時,可以使用 index 屬性指定參數的位置,index 屬性值從 0 開始。
package com.springlearn;
public class Man {
private String name;
private int age;
public Man(){
System.out.printf("在Man的構造函數內");
}
public Man(String name,int age){
this.name = name;
this.age = age;
System.out.printf("在Man的有參數構造函數內");
}
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;
}
public void show() {
System.out.println("名稱:" + name + "\n年齡:" + age);
}
}
package com.springlearn;
public class Person {
private Man man;
public Person(Man man) {
this.man = man;
}
public void man(){
man.show();
}
}
<?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-3.0.xsd">
<bean id="man" class="com.springlearn.Man">
<constructor-arg value="bianchengbang"></constructor-arg>
<constructor-arg value="12" type="int"></constructor-arg>
</bean>
<bean id="person" class="com.springlearn.Person">
<constructor-arg ref="man" type="com.springlearn.Man"></constructor-arg>
</bean>
</beans>
package com.springlearn;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Person person = (Person) context.getBean("person");
person.man();
}
}
在Man的有參數構造函數內
在Person的有參構造函數內
名稱:bianchengbang
年齡:12
setter注入
下面使用<property> 標簽實現 setter 注入。
在 <property> 標簽中,包含 name、ref、value 等屬性。name 用於指定參數名稱;value 屬性用於注入基本數據類型以及字符串類型的值;ref 屬性用於注入已經定義好的 Bean。
例 2
在例 1 的基礎上修改 Person 類的內容,代碼如下。
package com.springlearn;
public class Person {
private Man man;
public void man(){
man.show();
}
public Man getMan() {
return man;
}
public void setMan(Man man) {
System.out.println("在setMan方法內");
this.man = man;
}
}
<?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-3.0.xsd">
<bean id="person" class="com.springlearn.Person">
<property name="man" ref="man" />
</bean>
<bean id="man" class="com.springlearn.Man">
<property name="name" value="bianchengbang" />
<property name="age" value="12" />
</bean>
</beans>
輸出如下
在構造函數內
在setMan方法內
名稱:bianchengbang
年齡:12
Spring注入內部Bean
Java 中在類內部定義的類稱為內部類,同理在 Bean 中定義的 Bean 稱為內部 Bean。注入內部 Bean 使用 <property> 和 <constructor-arg> 中的 <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-3.0.xsd">
<bean id="outerBean" class="...">
<property name="target">
<!-- 定義內部Bean -->
<bean class="..." />
</property>
</bean>
</beans>
內部 Bean 的定義不需要指定 id 和 name 。如果指定了,容器也不會將其作為區分 Bean 的標識符,反而會無視內部 Bean 的 scope 屬性。所以內部 Bean 總是匿名的,而且總是隨着外部 Bean 創建。
在實際開發中很少注入內部 Bean,因為開發者無法將內部的 Bean 注入外部 Bean 以外的其它 Bean。
Person 類代碼如下。
public class Person {
private Man man;
public Man getMan() {
return man;
}
public void setMan(Man man) {
System.out.println("在setMan方法內");
this.man = man;
}
public void man() {
man.show();
}
}
Man
public class Man {
private String name;
private int age;
public Man() {
System.out.println("在man的構造函數內");
}
public Man(String name, int age) {
System.out.println("在man的有參構造函數內");
this.name = name;
this.age = age;
}
public void show() {
System.out.println("名稱:" + name + "\n年齡:" + 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-3.0.xsd">
<bean id="person" class="net.biancheng.Person">
<property name="man">
<bean class="net.biancheng.Man">
<property name="name" value="bianchengbang" />
<property name="age" value="12" />
</bean>
</property>
</bean>
</beans>
<?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-3.0.xsd">
<bean id="person" class="com.springlearn.Person">
<property name="man">
<bean class="com.springlearn.Man">
<property name="name" value="bianchengbang" />
<property name="age" value="12" />
</bean>
</property>
</bean>
</beans>
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Person person = (Person) context.getBean("person");
person.man();
}
}
運行結果
在man的有參構造函數內
在Person的構造函數內
名稱:bianchengbang
年齡:12
Spring注入集合
如果需要傳遞類似於 Java Collection 類型的值,例如 List、Set、Map 和 properties,可以使用 Spring 提供的集合配置標簽,如下表所示。
| 標簽 | 說明 |
|---|---|
<list> |
用於注入 list 類型的值,允許重復 |
<set> |
用於注入 set 類型的值,不允許重復 |
<map> |
用於注入 key-value 的集合,其中 key-value 可以是任意類型 |
<props> |
用於注入 key-value 的集合,其中 key-value 都是字符串類型 |
JavaCollection 類代碼如下。
import java.util.*;
public class JavaCollection {
List manList;
Set manSet;
Map manMap;
Properties manProp;
public void setManList(List manList) {
this.manList = manList;
}
public List getManList() {
System.out.println("List Elements :" + manList);
return manList;
}
public void setManSet(Set manSet) {
this.manSet = manSet;
}
public Set getManSet() {
System.out.println("Set Elements :" + manSet);
return manSet;
}
public void setManMap(Map manMap) {
this.manMap = manMap;
}
public Map getManMap() {
System.out.println("Map Elements :" + manMap);
return manMap;
}
public void setManProp(Properties manProp) {
this.manProp = manProp;
}
public Properties getManProp() {
System.out.println("Property Elements :" + manProp);
return manProp;
}
}
package com.springlearn;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
JavaCollection jc = (JavaCollection) context.getBean("javaCollection");
jc.getManList();
jc.getManSet();
jc.getManMap();
jc.getManProp();
}
}
<?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-3.0.xsd">
<bean id="javaCollection" class="com.springlearn.JavaCollection">
<property name="manList">
<list>
<value>編程幫</value>
<value>百度</value>
<value>C語言中文網</value>
<value>C語言中文網</value>
</list>
</property>
<property name="manSet">
<set>
<value>編程幫</value>
<value>百度</value>
<value>C語言中文網</value>
<value>C語言中文網</value>
</set>
</property>
<property name="manMap">
<map>
<entry key="1" value="編程幫" />
<entry key="2" value="百度" />
<entry key="3" value="C語言中文網" />
<entry key="4" value="C語言中文網" />
</map>
</property>
<property name="manProp">
<props>
<prop key="one">編程幫</prop>
<prop key="one">編程幫</prop>
<prop key="two">百度</prop>
<prop key="three">C語言中文網</prop>
<prop key="four">C語言中文網</prop>
</props>
</property>
</bean>
</beans>
List Elements :[編程幫, 百度, C語言中文網, C語言中文網]
Set Elements :[編程幫, 百度, C語言中文網]
Map Elements :{1=編程幫, 2=百度, 3=C語言中文網, 4=C語言中文網}
Property Elements :{four=C語言中文網, one=編程幫, two=百度, three=C語言中文網}
注入Bean引用
也可以在集合元素中注入 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-3.0.xsd">
<bean id="..." class="...">
<property name="manList">
<list>
<ref bean="man1" />
<ref bean="man2" />
<value>編程幫</value>
</list>
</property>
<property name="manSet">
<set>
<ref bean="man1" />
<ref bean="man2" />
<value>編程幫</value>
</set>
</property>
<property name="manMap">
<map>
<entry key="one" value="編程幫" />
<entry key="two" value-ref="man1" />
<entry key="three" value-ref="man2" />
</map>
</property>
</bean>
</beans>
注入null和空字符串的值
Spring 會把屬性的空參數直接當成空字符串來處理,如果您需要傳遞一個空字符串值,可以這樣寫:
<bean id = "..." class = "exampleBean">
<property name = "email" value = ""/>
</bean>
等效於以下代碼
exampleBean.setEmail("")
如果需要傳遞 NULL 值,
<bean id = "..." class = "exampleBean">
<property name = "email">
<null/>
</property>
</bean>
等效於以下代碼
exampleBean.setEmail(null)
Spring Bean 自動裝配
Bean的裝配可以理解為依賴關系注入,Bean的裝配方式也就是Bean的依賴注入方式。Spring容器支持多種裝配Bean的方式,如基於XML的Bean裝配、基於Annotation的Bean裝配和自動裝配等。
Spring 基於 XML 的裝配通常采用兩種實現方式。即setter 注入和構造注入。
自動裝配就是指 Spring 容器在不使用 <constructor-arg> 和<property> 標簽的情況下,可以自動裝配(autowire)相互協作的 Bean 之間的關聯關系,將一個 Bean 注入其他 Bean 的 Property 中。
使用自動裝配需要配置 <bean> 元素的 autowire 屬性。autowire 屬性有五個值,具體說明如下表所示。
autowire 的屬性和作用
| 名稱 | 說明 |
|---|---|
| no | 默認值,表示不使用自動裝配,Bean 依賴必須通過 ref 元素定義。 |
| byName | 根據 Property 的 name 自動裝配,如果一個 Bean 的 name 和另一個 Bean 中的 Property 的 name 相同,則自動裝配這個 Bean 到 Property 中。 |
| byType | 根據 Property 的數據類型(Type)自動裝配,如果一個 Bean 的數據類型兼容另一個 Bean 中 Property 的數據類型,則自動裝配。 |
| constructor | 類似於 byType,根據構造方法參數的數據類型,進行 byType 模式的自動裝配。 |
| autodetect(3.0版本不支持) | 如果 Bean 中有默認的構造方法,則用 constructor 模式,否則用 byType 模式。 |
示例
Person 類代碼如下。
public class Person {
private Man man;
public Person() {
System.out.println("在Person的構造函數內");
}
public Person(Man man) {
System.out.println("在Person的有參構造函數內");
this.man = man;
}
public void man() {
man.show();
}
public Man getMan() {
return man;
}
public void setMan(Man man) {
this.man = man;
}
}
Man類如下
public class Man {
private String name;
private int age;
public Man() {
System.out.println("在man的構造函數內");
}
public Man(String name, int age) {
System.out.println("在man的有參構造函數內");
this.name = name;
this.age = age;
}
public void show() {
System.out.println("名稱:" + name + "\n年齡:" + 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;
}
}
MainApp
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
Person person = (Person) context.getBean("person");
person.man();
}
}
-
不使用自動裝配(autowire="no")
autowire="no" 表示不使用自動裝配,需要手動注入,Bean 依賴通過 ref 元素定義,Beans.xml 配置文件如下。
<?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-3.0.xsd"> <bean id="man" class="com.springlearn.Man"> <constructor-arg value="bianchengbang" /> <constructor-arg value="12" type="int" /> </bean> <bean id="person" class="com.springlearn.Person" autowire="no"> <constructor-arg ref="man" type="com.springlearn.Man"/> </bean> </beans>運行結果如下。
在man的有參構造函數內 在Person的有參構造函數內 名稱:bianchengbang 年齡:12 -
按名稱自動裝配(autowire="byName")
autowire="byName"表示按屬性名稱自動裝配,XML 文件中 Bean 的 id 必須與類中的屬性名稱相同。配置文件內容修改如下。<?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-3.0.xsd"> <bean id="man" class="com.springlearn.Man"> <constructor-arg value="bianchengbang" /> <constructor-arg value="12" type="int" /> </bean> <bean id="person" class="com.springlearn.Person" autowire="byName" /> </beans>運行結果如下
在man的有參構造函數內 在Person的構造函數內 名稱:bianchengbang 年齡:12如果更改 Bean 的名稱,很可能不會注入依賴項。
將 Bean 的名稱更改為 man1,配置文件如下:
<?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-3.0.xsd"> <bean id="man1" class="com.springlearn.Man"> <constructor-arg value="bianchengbang" /> <constructor-arg value="12" type="int" /> </bean> <bean id="person" class="com.springlearn.Person" autowire="byName" /> </beans>注入失敗,異常信息為:
Exception in thread "main" java.lang.NullPointerException at com.springlearn.Person.man(Person.java:13) at com.springlearn.MainApp.main(MainApp.java:11) -
按類型自動裝配(autowire="byType")
XML 文件中 Bean 的 id 與類中的屬性名稱可以不同,但必須只有一個類型的 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-3.0.xsd"> <bean id="man1" class="com.springlearn.Man"> <constructor-arg value="bianchengbang" /> <constructor-arg value="12" type="int" /> </bean> <bean id="person" class="com.springlearn.Person" autowire="byType" /> </beans>結果
在man的有參構造函數內 在Person的構造函數內 名稱:bianchengbang 年齡:12如果您有相同類型的多個 Bean,則注入失敗,並且引發異常。
添加 id 為 man2 的 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-3.0.xsd"> <bean id="man1" class="com.springlearn.Man"> <constructor-arg value="bianchengbang" /> <constructor-arg value="12" type="int" /> </bean> <bean id="man2" class="com.springlearn.Man"> <constructor-arg value="bianchengbang" /> <constructor-arg value="12" type="int" /> </bean> <bean id="person" class="com.springlearn.Person" autowire="byType" /> </beans>異常信息為:
在man的有參構造函數內 在man的有參構造函數內 在Person的構造函數內 一月 26, 2021 1:34:14 下午 org.springframework.context.support.AbstractApplicationContext refresh 警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'person' defined in class path resource [Beans.xml]: Unsatisfied dependency expressed through bean property 'man'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'net.biancheng.Man' available: expected single matching bean but found 2: man1,man2 ... -
構造函數自動裝配(autowire="constructor")
<?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-3.0.xsd"> <bean id="man1" class="com.springlearn.Man"> <constructor-arg value="bianchengbang" /> <constructor-arg value="12" type="int" /> </bean> <bean id="person" class="com.springlearn.Person" autowire="constructor" /> </beans>在man的有參構造函數內 在Person的有參構造函數內 名稱:bianchengbang 年齡:12
自動裝配的優缺點
優點
- 自動裝配只需要較少的代碼就可以實現依賴注入。
缺點
- 不能自動裝配簡單數據類型,比如 int、boolean、String 等。
- 相比較顯示裝配,自動裝配不受程序員控制。
