1、Spring 概述
1.1、Spring 的概念和特點
Spring 是一個輕量級的控制反轉(IoC)和麵向切面(AOP)的開源容器框架,它是由 Rod Johnson(音樂學博士)所建立,其核心就是為了解決企業應用開發的複雜性。
Spring 是一款目前主流的 Java EE 輕量級開源框架,是 Java 世界最為成功的框架之一,自 2004 年 4 月,Spring 1.0 版本正式釋出以來,Spring 已經步入到了第 5 個大版本,也就是我們常說的 Spring 5。本教程使用版本為 Spring 5.3.22。
Spring 自誕生以來備受青睞,一直被廣大開發人員作為 Java 企業級應用程式開發的首選。時至今日,Spring 儼然成為了 Java EE 代名詞,成為了構建 Java EE 應用的事實標準。
Spring 框架不侷限於伺服器端的開發。從簡單性、可測試性和鬆耦合的角度而言,任何 Java 應用都可以從 Spring 中受益。Spring 框架還是一個超級粘合平臺,除了自己提供功能外,還提供粘合其他技術和框架的能力。
Spring 致力於提供一個以統一的、高效的方式構造整個應用,並且可以將單層框架以最佳的組合揉和在一起建立一個連貫的體系。例如,通過 Spring 框架,整合 MyBatis 、SpringMVC等,也就是傳說中的大雜燴。其主要具有以下特點:
-
方便解耦,簡化開發:管理所有物件的建立和依賴的關係維護。
-
AOP程式設計的支援:方便的實現對程式進行許可權攔截、執行監控等功能。
-
宣告式事務的支援:通過配置完成對事務的管理,而無需手動程式設計。
-
方便程式的測試:Spring對Junit4支援,可以通過註解方便的測試Spring程式。
-
方便整合各種優秀框架:內部提供了對各種優秀框架的直接支援。
-
降低JavaEE API的使用難度:封裝JavaEE開發中非常難用的一些API(JDBC、JavaMail、遠端呼叫等)。
1.2、Spring 的狹義和廣義
在不同的語境中,Spring 所代表的含義是不同的。下面我們就分別從“廣義”和“狹義”兩個角度,對 Spring 進行介紹。
廣義上的 Spring 泛指以 Spring Framework 為核心的 Spring 技術棧。
經過十多年的發展,Spring 已經不再是一個單純的應用框架,而是逐漸發展成為一個由多個不同子專案(模組)組成的成熟技術,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子專案的基礎。
這些子專案涵蓋了從企業級應用開發到雲端計算等各方面的內容,能夠幫助開發人員解決軟體發展過程中不斷產生的各種實際問題,給開發人員帶來了更好的開發體驗,子專案主要有以下幾種:
-
Spring Data:Spring 提供的資料訪問模組,對 JDBC 和 ORM 提供了很好的支援。通過它,開發人員可以使用一種相對統一的方式,來訪問位於不同型別資料庫中的資料。
-
Spring Batch:款專門針對企業級系統中的日常批處理任務的輕量級框架,能夠幫助開發人員方便的開發出健壯、高效的批處理應用程式。
-
Spring Security:前身為 Acegi,是 Spring 中較成熟的子模組之一。它是一款可以定製化的身份驗證和訪問控制框架。
-
Spring Mobile:是對 Spring MVC 的擴充套件,用來簡化移動端 Web 應用的開發。
-
Spring Boot:是 Spring 團隊提供的全新框架,它為 Spring 以及第三方庫一些開箱即用的配置,可以簡化 Spring 應用的搭建及開發過程。
-
Spring Cloud:一款基於 Spring Boot 實現的微服務框架。它並不是某一門技術,而是一系列微服務解決方案或框架的有序集合。它將市面上成熟的、經過驗證的微服務框架整合起來,並通過 Spring Boot 的思想進行再封裝,遮蔽調其中複雜的配置和實現原理,最終為開發人員提供了一套簡單易懂、易部署和易維護的分散式系統開發工具包。
狹義的 Spring 特指 Spring Framework,通常我們將它稱為 Spring 框架。
Spring 框架是一個分層的、面向切面的 Java 應用程式的一站式輕量級解決方案,它是 Spring 技術棧的核心和基礎,是為了解決企業級應用開發的複雜性而建立的。
-
IOC:Inverse of Control 的簡寫,譯為“控制反轉”,指把建立物件過程交給 Spring 進行管理。
-
AOP:Aspect Oriented Programming 的簡寫,譯為“面向切面程式設計”。
AOP 用來封裝多個類的公共行為,將那些與業務無關,卻為業務模組所共同呼叫的邏輯封裝起來,減少系統的重複程式碼,降低模組間的耦合度。另外,AOP 還解決一些系統層面上的問題,比如日誌、事務、許可權等。
Spring 是一種基於 Bean 的程式設計技術,它深刻地改變著 Java 開發世界。Spring 使用簡單、基本的 Java Bean 來完成以前只有 EJB 才能完成的工作,使得很多複雜的程式碼變得優雅和簡潔,避免了 EJB 臃腫、低效的開發模式,極大的方便專案的後期維護、升級和擴充套件。
在實際開發中,伺服器端應用程式通常採用三層體系架構,分別為表現層(web)、業務邏輯層(service)、持久層(dao)。
Spring 致力於 Java EE 應用各層的解決方案,對每一層都提供了技術支援,例如表現成提供了對 Spring MVC、Struts2 等框架的整合;在業務邏輯層提供了管理事務和記錄日誌的功能;在持久層還可以整合 MyBatis、Hibernate 和 JdbcTemplate 等技術,對資料庫進行訪問。
這充分地體現了 Spring 是一個全面的解決方案,對於那些已經有較好解決方案的領域,Spring 絕不做重複的事情,這一理念即為“輪子理論”。
“輪子理論”,也即“不要重複發明輪子”,這是西方國家的一句諺語,意思是別人已經做過,我們需要用的時候,直接拿來用即可,而不要重新制造。將已出現的各種IT技術比作一個個“輪子”,當我們進行專案開發的時候,若已有的技術能滿足我們的開發需求,我們不需要在去創造新的技術,只需要把現有的技術拿過來用就可以了。若已有的技術不能滿足我們的開發需求,這時,我們就要去創造新的“輪子”。
1.3、Spring 的體系結構
Spring 框架基本涵蓋了企業級應用開發的各個方面,它包含了 20 多個不同的模組。下圖中包含了 Spring 框架的所有模組,這些模組可以滿足一切企業級應用開發的需求,在開發過程中可以根據需求有選擇性地使用所需要的模組。下面分別對這些模組的作用進行簡單介紹。
1.3.1、Core Container(Spring 的核心容器)
Spring 的核心容器是其他模組建立的基礎,由 Beans 模組、Core 核心模組、Context 上下文模組和 SpEL 表示式語言模組組成,沒有這些核心容器,也不可能有 AOP、Web 等上層的功能。具體介紹如下。
-
spring-core模組:封裝了 Spring 框架的底層部分,包括資源訪問、型別轉換及一些常用工具類。。
-
spring-beans模組:提供了BeanFactory與Bean的裝配,使Spring成為一個容器,也就是提供了框架的基本組成部分,包括控制反轉(IOC)和依賴注入(DI)功能
-
spring-context模組:應用上下文,建立在 Core 和 Beans 模組的基礎之上,整合 Beans 模組功能並新增資源繫結、資料驗證、國際化、Java EE 支援、容器生命週期、事件傳播等,提供一個框架式的物件訪問方式,是訪問定義和配置任何物件的媒介,使Spring成為一個框架。ApplicationContext 介面是上下文模組的焦點。
-
spring-context-support模組:支援整合第三方庫到Spring應用程式上下文,特別是用於快取記憶體(EhCache、JCache)和任務排程(CommonJ、Quartz)的支援。
-
spring-expression(SpELl)模組:Spring 表示式語言全稱為“Spring Expression Language”,縮寫為“SpEL”,提供了強大的表示式語言支援,支援訪問和修改屬性值,方法呼叫,支援訪問及修改陣列、容器和索引器,命名變數,支援算數和邏輯運算,支援從 Spring 容器獲取 Bean,它也支援列表投影、選擇和一般的列表聚合等。
1.3.2、AOP、Aspects、Instrumentation 和 Messaging(中間層)
在 Core Container 之上是 AOP、Aspects 等模組,具體介紹如下:
-
spring-aop模組:提供了一個符合 AOP 要求的面向切面的程式設計實現,允許定義方法攔截器和切入點,將程式碼按照功能進行分離,以便乾淨地解耦。提供了面向切面程式設計實現,提供比如日誌記錄、許可權控制、效能統計等通用功能和業務邏輯分離的技術,並且能動態的把這些功能新增到需要的程式碼中,這樣各司其職,降低業務邏輯和通用功能的耦合。
-
spring-aspects模組:提供了與 AspectJ 的整合功能,AspectJ是 一個功能強大且成熟的面向切面程式設計(AOP) 框架。
-
spring-instrument模組:提供了類工具支援和類載入器的實現,可以在特定的應用伺服器中使用。
-
spring-messaging模組:Spring 4.0 以後新增了訊息模組,該模組提供了對訊息傳遞體系結構和協議的支援。
1.3.3、Data Access/Integration(資料訪問/整合)
資料訪問/整合層包括 JDBC、ORM、OXM、JMS 和 Transactions 模組,具體介紹如下。
-
spring-jdbc模組:提供了一個 JBDC 的樣例模板,,使用這些模板能消除傳統冗長的 JDBC 編碼還有必須的事務控制,消除了煩瑣的JDBC編碼和資料庫廠商特有的錯誤程式碼解析,而且能享受到 Spring 管理事務的好處。
-
spring-orm模組:提供一個物件關係對映(Object-Relational Mapping)API 框架,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且還可以使用 Spring 事務管理,無需額外控制事務。
-
spring-oxm模組:提供了一個支援 Object /XML 對映的抽象層實現,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。將 Java 物件對映成 XML 資料,或者將XML 資料對映成 Java 物件。
-
spring-jms模組:指 Java 訊息服務,提供一套 “訊息生產者、訊息消費者”模板用於更加簡單的使用 JMS,JMS 用於用於在兩個應用程式之間,或分散式系統中傳送訊息,進行非同步通訊。
-
spring-tx模組:事務模組,支援用於實現特殊介面和所有POJO類的程式設計和宣告式事務管理。
1.3.4、Web 模組
Spring 的 Web 層包括 Web、Servlet、WebSocket 和 Portlet 元件,具體介紹如下。
-
spring-web模組:提供了基本的 Web 開發整合特性,例如多檔案上傳功能、使用的 Servlet 監聽器的 IOC 容器初始化以及 Web 應用上下文。
-
spring-webmvc模組:提供了一個 Spring MVC Web 框架實現。Spring MVC 框架提供了基於註解的請求資源注入、更簡單的資料繫結、資料驗證等及一套非常易用的 JSP 標籤,完全無縫與 Spring 其他技術協作。
-
spring-websocket模組:提供了簡單的介面,使用者只要實現響應的介面就可以快速的搭建 WebSocket Server,從而實現雙向通訊。
-
spring-portlet模組:提供了在 Portlet 環境中使用 MVC 實現,類似 Web-Servlet 模組的功能。
1.4 Spring Hello World
首先我們需要建立一個 Maven 專案,建立成功後,我們需要匯入 Spring 的相關依賴,這裡推薦直接匯入 spring-webmvc,這個 jar 包預設會為我們匯入其他的依賴包,匯入如下:
<?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">
<parent>
<artifactId>JavaEE</artifactId>
<groupId>com.loner</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Spring</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.22</version>
</dependency>
</dependencies>
</project>
依賴匯入成功後,等待 Maven 拉取即可,接下來我們需要建立一個 HelloWorld 實體類,此類只有一個字串了型別的欄位,生成其 getter 和 setter 以及 toString()方法即可,實體類實現如下:
public class HelloWorld {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "HelloWorld{" + "message='" + message + '\'' + '}';
}
}
實體類建立成功後,我們就需要使用 Spring 的特有方式來裝配 Bean,這也是 Spring 的核心思想,將物件的建立和銷燬完全交付給 Spring 進行管理,我們只需要進行簡單的配置即可使用。首先我們需要在 resource 目錄下建立一個 Spring 的 xml 配置檔案,名稱隨意,推薦使用 applicationContext.xml。建立成功後,需要引入 Spring 各個模組的約束,然後通過 <bean></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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 裝配Bean,相當於new一個HelloWorld物件,id等同於變數名,class就是類路徑 -->
<bean id="helloWorld" class="com.loner.mj.pojo.HelloWorld">
<!-- 設定屬性及其對應的值,name為實體的屬性名,value為屬性的值 -->
<property name="message" value="第一個Spring程式" />
</bean>
</beans>
注意,這裡的 Bean 裝配是通過 xml 配置檔案的方式實現的,還有註解的實現方式以及 Java 配置類的實現方式等。
Bean 裝配後,我們需要通過 Spring 的入口進行應用的啟動,此時我們需要例項化 ClassPathXmlApplicationContext 物件,此物件需要傳入一個或多個 xml 配置檔案,他是 ApplicationContext 介面的一個實現類,此介面的實現類有很多,這裡我們只使用 ClassPathXmlApplicationContext 物件,實現方式如下:
public class HelloTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// getBean的屬性值就是Bean標籤的id值
HelloWorld bean = (HelloWorld) applicationContext.getBean("helloWorld");
System.out.println(bean.getMessage());
}
}
到此為止,整個 Spring 應用即編寫完成,啟動程式後,控制檯輸出“第一個Spring程式”。從以上程式可以看出,我們從未手動的例項化過物件,我們只是在 Spring 的配置檔案中,進行了 Bean 的裝配,然後就可以在程式中使用此 Bean,這也印證了,Spring 框架無需我們考慮和關心 Bean 在何時例項化,我們只需要關心自己的核心業務即可,所有的 Bean 的建立和銷燬都是 Spring 框架幫我們完成,因此接下來我們需要對這個框架的使用以及相關的原理進行探討。
2、Spring IoC
2.1、IoC 和 DI 概述
2.1.1、IoC(控制反轉) 的概念
IoC 是 Inversion of Control 的簡寫,譯為“控制反轉”,於1996年,Michael Mattson在一篇有關探討物件導向框架的文章中提出。它不是一門技術,而是一種設計思想,是一個重要的物件導向程式設計法則,能夠指導我們如何設計出鬆耦合、更優良的程式。
Spring 通過 IoC 容器來管理所有 Java 物件的例項化和初始化,控制物件與物件之間的依賴關係。我們將由 IoC 容器管理的 Java 物件稱為 Spring Bean,它與使用關鍵字 new 建立的 Java 物件沒有任何區別。IoC 容器是 Spring 框架中最重要的核心元件之一,它貫穿了 Spring 從誕生到成長的整個過程。因此,IoC 的作用就是完成物件的建立和依賴的管理注入。
在傳統的 Java 應用中,一個類想要呼叫另一個類中的屬性或方法,通常會先在其程式碼中通過 new Object() 的方式將後者的物件建立出來,然後才能實現屬性或方法的呼叫。為了方便理解和描述,我們可以將前者稱為“呼叫者”,將後者稱為“被呼叫者”,也就是說,呼叫者掌握著被呼叫者物件建立的控制權。
但在 Spring 應用中,Java 物件建立的控制權是掌握在 IoC 容器手裡的,其大致步驟如下。
-
開發人員通過 XML 配置檔案、註解、Java 配置類等方式,對 Java 物件進行定義,例如在 XML 配置檔案中使用
<bean>
標籤、在 Java 類上使用 @Component 註解等。 -
Spring 啟動時,IoC 容器會自動根據物件定義,將這些物件建立並管理起來。這些被 IoC 容器建立並管理的物件被稱為 Spring Bean。
-
當我們想要使用某個 Bean 時,可以直接從 IoC 容器中獲取(例如通過 ApplicationContext 的 getBean() 方法),而不需要手動通過程式碼(例如 new Obejct() 的方式)建立。
IoC 帶來的最大改變不是程式碼層面的,而是從思想層面上發生了“主從換位”的改變。原本呼叫者是主動的一方,它想要使用什麼資源就會主動出擊,自己建立;但在 Spring 應用中,IoC 容器掌握著主動權,呼叫者則變成了被動的一方,被動的等待 IoC 容器建立它所需要的物件(Bean)。
這個過程在職責層面發生了控制權的反轉,把原本呼叫者通過程式碼實現的物件的建立,反轉給 IoC 容器來幫忙實現,因此我們將這個過程稱為 Spring 的“控制反轉”。簡單的理解就是原呼叫這相當於程式設計師,而 IoC 相當於使用者,使用者根據市場的變化隨時修改程式,程式提供給使用者以配置的方式來實現,而不需要大量的修改原始碼來實現
2.1.2、DI(依賴注入) 的概念
依賴注入(Denpendency Injection,簡寫為 DI)是 Martin Fowler 在 2004 年在對“控制反轉”進行解釋時提出的。Martin Fowler 認為“控制反轉”一詞很晦澀,無法讓人很直接的理解“到底是哪裡反轉了”,因此他建議使用“依賴注入”來代替“控制反轉”。
在物件導向中,物件和物件之間是存在一種叫做“依賴”的關係。簡單來說,依賴關係就是在一個物件中需要用到另外一個物件,即物件中存在一個屬性,該屬性是另外一個類的物件,這種依賴一般都組合關係,還有一種就是繼承關係。
總結來說,依賴注入就是在 IoC 容器執行期間,動態地將某種依賴關係注入到物件之中,也就是說獲得依賴物件的過程被反轉了。依賴注入(DI)和控制反轉(IoC)是從不同的角度的描述的同一件事情,就是指通過引入 IoC 容器,利用依賴關係注入的方式,實現物件之間的解耦。
例如,有一個名為 A 的 Java 類,它的程式碼如下:
public class A {
String bid;
B b;
}
從程式碼可以看出,A 中存在一個 B 型別的物件屬性 b,此時我們就可以說 A 的物件依賴於物件 b(A 和 B 是組合關係),而依賴注入就是基於這種“依賴關係”而產生的。
我們知道,控制反轉核心思想就是由 Spring 負責物件的建立。在物件建立過程中,Spring 會自動根據依賴關係,將它依賴的物件注入到當前物件中,這就是所謂的“依賴注入”。
依賴注入本質上是 Spring Bean 屬性注入的一種,只不過這個屬性是一個物件屬性而已。
2.1.3、IoC 的工作原理
在 Java 軟體開發過程中,系統中的各個物件之間、各個模組之間、軟體系統和硬體系統之間,或多或少都存在一定的耦合關係。
若一個系統的耦合度過高,那麼就會造成難以維護的問題,但完全沒有耦合的程式碼幾乎無法完成任何工作,這是由於幾乎所有的功能都需要程式碼之間相互協作、相互依賴才能完成。因此我們在程式設計時,所秉承的思想一般都是在不影響系統功能的前提下,最大限度的降低耦合度。
IoC 底層通過工廠模式、Java 的反射機制、XML 解析等技術,將程式碼的耦合度降低到最低限度,其主要步驟如下:
-
首先在配置檔案(例如 Bean.xml)中,對各個物件以及它們之間的依賴關係進行配置;
-
我們可以把 IoC 容器當做一個工廠,這個工廠的產品就是 Spring Bean;
-
容器啟動時會載入並解析這些配置檔案,得到物件的基本資訊以及它們之間的依賴關係;
-
IoC 利用 Java 的反射機制,根據類名生成相應的物件(即 Spring Bean),並根據依賴關係將這個物件注入到依賴它的物件中。
由於物件的基本資訊、物件之間的依賴關係都是在配置檔案中定義的,並沒有在程式碼中緊密耦合,因此即使物件發生改變,我們也只需要在配置檔案中進行修改即可,而無須對 Java 程式碼進行修改,這就是 Spring IoC 實現解耦的原理。
Spring 通過“輪子理論”進行設計,將傳統的多個物件之間的複雜依賴關係,通過 IOC 容器進行解耦,把物件的建立和依賴交給 Spring 處理,使得各個物件之間的依賴由主動變為被動,把複雜系統分解成相互合作的物件,這些物件類通過封裝以後,內部實現對外部是透明的,從而降低了解決問題的複雜度,而且可以靈活地被重用和擴充套件。IoC 相當於“粘合劑”,其整個實現思想如下圖所示:
2.1.4、IoC 容器的兩種實現
IoC 思想基於 IoC 容器實現的,IoC 容器底層其實就是一個 Bean 工廠。Spring 框架為我們提供了兩種不同型別 IoC 容器,它們分別是 BeanFactory 和 ApplicationContext。
BeanFactory 是 IoC 容器的基本實現,也是 Spring 提供的最簡單的 IoC 容器,它提供了 IoC 容器最基本的功能,由 org.springframework.beans.factory.BeanFactory
介面定義。最典型的Bean工廠,定義了IoC容器的基本功能規範。
BeanFactory 採用懶載入(lazy-load)機制,容器在載入配置檔案時並不會立刻建立 Java 物件,只有程式中獲取(使用)這個物件時才會建立,具體實現如下:
public static void main(String[] args) {
BeanFactory context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = context.getBean("helloWorld", HelloWorld.class);
obj.getMessage();
}
注意:BeanFactory 是 Spring 內部使用介面,通常情況下不提供給開發人員使用,同時不建議使用此方式。
ApplicationContext 是 BeanFactory 介面的子介面,是對 BeanFactory 的擴充套件。ApplicationContext 在 BeanFactory 的基礎上增加了許多企業級的功能,例如 AOP(面向切面程式設計)、國際化、事務支援等。ApplicationContext 介面有兩個常用的實現類,具體如下:
-
ClassPathXmlApplicationContext:載入類路徑 ClassPath 下指定的 XML 配置檔案,並完成 ApplicationContext 的例項化工作。
-
FileSystemXmlApplicationContext:載入指定的檔案系統路徑中指定的 XML 配置檔案,並完成 ApplicationContext 的例項化工作。
例如,修改上述第一個 Spring 程式中的例項化方法,使其能夠載入指定檔案系統路徑中的配置檔案,具體實現如下:
public static void main(String[] args) {
//使用 FileSystemXmlApplicationContext 載入指定路徑下的配置檔案 Bean.xml
ApplicationContext context = new FileSystemXmlApplicationContext("D:\\eclipe workspace\\spring workspace\\HelloSpring\\src\\Beans.xml");
HelloWorld obj = context.getBean("helloWorld", HelloWorld.class);
obj.getMessage();
}
2.2、Spring Bean
2.2.1、Bean 的定義
由 Spring IoC 容器管理的物件稱為 Bean,Bean 根據 Spring 配置檔案中的資訊建立。我們可以把 Spring IoC 容器看作是一個大工廠,Bean 相當於工廠的產品。如果希望這個大工廠生產和管理 Bean,就需要告訴容器需要哪些 Bean,以哪種方式裝配。
Spring 配置檔案支援兩種格式,即 XML 檔案格式和 Properties 檔案格式。
-
Properties 配置檔案主要以 key-value 鍵值對的形式存在,只能賦值,不能進行其他操作,適用於簡單的屬性配置。
-
XML 配置檔案採用樹形結構,結構清晰,相較於 Properties 檔案更加靈活。但是 XML 配置比較繁瑣,適用於大型的複雜的專案。
通常情況下,Spring 的配置檔案都是使用 XML 格式的。XML 配置檔案的根元素是 <beans>
,該元素包含了多個子元素 <bean>
。每一個 <bean>
元素都定義了一個 Bean,並描述了該 Bean 是如何被裝配到 Spring 容器中的。例如在Spring Hello World中的配置一樣。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helloWorld" class="com.loner.mj.pojo.HelloWorld">
<property name="message" value="第一個Spring程式" />
</bean>
</beans>
在 XML 配置的 <beans>
元素中可以包含多個屬性或子元素,常用的屬性或子元素如下所示:
-
id:Bean 的唯一識別符號(變數名),Spring IoC 容器對 Bean 的配置和管理都通過該屬性完成。id 的值必須以字母開始,可以使用字母、數字、下劃線等符號。
-
name:該屬性表示 Bean 的名稱(別名),我們可以通過 name 屬性為同一個 Bean 同時指定多個名稱,每個名稱之間用逗號或分號隔開。Spring 容器可以通過 name 屬性配置和管理容器中的 Bean。
-
class:該屬性指定了 Bean 的具體實現類,它必須是一個完整的類名,即類的全限定名。
-
scope:表示 Bean 的作用域,屬性值可以為 singleton(單例)、prototype(原型)、request、session 和 - global Session。預設值是 singleton。
-
constructor-arg:
<bean>
元素的子元素,我們可以通過該元素,將構造引數傳入,以實現 Bean 的例項化。該元素的 index 屬性指定構造引數的序號(從 0 開始),type 屬性指定構造引數的型別。 -
property:
<bean>
元素的子元素,用於呼叫 Bean 例項中的 setter 方法對屬性進行賦值,從而完成屬性的注入。該元素的 name 屬性用於指定 Bean 例項中相應的屬性名。 -
ref:
<property> 和 <constructor-arg>
等元素的子元索,用於指定對某個 Bean 例項的引用,即<bean>
元素中的 id 或 name 屬性。 -
value:
<property> 和 <constractor-arg>
等元素的子元素,用於直接指定一個常量值。 -
list、set、map:用於封裝 List 或陣列型別、Set型別以及Map型別的屬性注入。
-
entry:
<map>
元素的子元素,用於設定一個鍵值對。其 key 屬性指定字串型別的鍵值,ref 或 value 子元素指定其值。 -
init-method:容器載入 Bean 時呼叫該方法,類似於 Servlet 中的 init() 方法
-
destroy-method:容器刪除 Bean 時呼叫該方法,類似於 Servlet 中的 destroy() 方法。該方法只在 scope=singleton 時有效。
-
lazy-init:懶載入,值為 true,容器在首次請求時才會建立 Bean 例項;值為 false,容器在啟動時建立 Bean 例項。該方法只在 scope=singleton 時有效
2.2.2、Bean 的依賴注入
Spring 主要有三種屬性注入的方式,分貝是建構函式注入、setter注入(又稱屬性注入)以及擴充注入方式。在演示三種注入方式之前,我們首先建立一個用於測試的 Bean。
public class Student {
private String name; //基本型別
private Classes classes; // 物件型別
private String[] array; // 陣列型別
private List<String> list; // List集合型別
private Map<String, String> map; // Map集合型別
private Set<Integer> set; // Set集合型別
private Properties properties; // 配置檔案型別
// 此處省略getter、setter、有參構造以及toString
}
建構函式注入
通過 Bean 的帶參建構函式注入時,首先要在 Bean 中新增一個有參建構函式,建構函式內的每一個引數代表一個需要注入的屬性,其次需要在 <bean>
元素內使用 <constructor-arg>
元素,對建構函式內的屬性進行賦值,Bean 的建構函式內有多少引數,就需要使用多少個 <constructor-arg>
元素,此標籤的屬性和<property>
一樣,其實現主要如下:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 建構函式注入 -->
<bean id="student" class="com.loner.mj.pojo.Student">
<constructor-arg name="name" value="張三" />
</bean>
</beans>
setter 注入(屬性注入)
在 Spring 例項化 Bean 的過程中,IoC 容器首先會呼叫預設的構造方法(無參構造方法)例項化 Bean(Java 物件),然後通過 Java 的反射機制呼叫這個 Bean 的 setXxx() 方法,將屬性值注入到 Bean 中。
使用 setter 注入的方式進行屬性注入,首先需要在 Bean 中提供一個預設的無參建構函式(在沒有其他帶參建構函式的情況下,可省略),併為所有需要注入的屬性提供一個 setXxx() 方法,其次需要在 xml 配置檔案使用 <bean>
元素內使用 <property>
元素對各個屬性進行賦值,其實現主要如下:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 依賴注入 -->
<bean id="classes" class="com.loner.mj.pojo.Classes">
<property name="name" value="一年級1班" />
</bean>
<!-- setter注入(屬性注入) -->
<bean id="student2" class="com.loner.mj.pojo.Student">
<!-- 基本型別 -->
<property name="name" value="張三" />
<!-- 物件型別 -->
<property name="classes" ref="classes" />
<!-- 陣列型別 -->
<property name="array">
<array>
<value>元素1</value>
<value>元素2</value>
</array>
</property>
<!-- list集合型別 -->
<property name="list">
<list>
<value>元素1</value>
<value>元素1</value>
</list>
</property>
<!-- set集合型別 -->
<property name="set">
<set>
<value>1</value>
<value>2</value>
</set>
</property>
<!-- map集合型別 -->
<property name="map">
<map>
<entry key="鍵" value="值" />
</map>
</property>
<!-- 配置檔案型別 -->
<property name="properties">
<props>
<prop key="鍵">值</prop>
</props>
</property>
</bean>
</beans>
程式執行之後,輸出的結果為:Student{name='張三', classes=Classes{name='一年級1班'}, array=[元素1, 元素2], list=[元素1, 元素1], map={鍵=值}, set=[1, 2], properties={鍵=值}}
擴充方式注入
我們在通過建構函式或 setter 方法進行屬性注入時,通常是在 <bean>
元素中巢狀 <property> 和 <constructor-arg>
元素來實現的,這種方式雖然結構清晰,但書寫較繁瑣。
Spring 給我們提供了兩種擴充方式來實現依賴注入,分別是p 名稱空間和c名稱空間
,其中p名稱空間是 setter 方式屬性注入的一種快捷實現方式,c名稱空間是建構函式注入的一種快捷實現方式。
通過它兩,我們能夠以 bean 屬性的形式實現 setter 方式的屬性注入和建構函式注入,而不再使用巢狀的 <property> 和 <constructor-arg>
元素,以實現簡化 Spring 的 XML 配置的目的。
使用兩種擴充方式之前,首先要確保 Bean 生成了 setter 方法,並且使用c名稱空間時,需要確保具有無參和有參構造才可以,然後我們需要在 xml 配置檔案引入兩者的約束,其約束如下:
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
引入約束後,通過c:引數名和p:屬性名
的方式就可以給 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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 擴充方式注入 -->
<bean id="Bean 唯一標誌符" class="包名+類名" p:普通屬性="普通屬性值" p:物件屬性-ref="物件的引用">
<bean id="student3" class="com.loner.mj.pojo.Student" c:name="劉備"></bean>
<bean id="student4" class="com.loner.mj.pojo.Student" p:name="張三"></bean>
</beans>
2.2.3、Bean 的作用域
預設情況下,所有的 Spring Bean 都是單例的,也就是說在整個 Spring 應用中, Bean 的例項只有一個。
我們可以在 <bean>
元素中新增 scope 屬性來配置 Spring Bean 的作用範圍。例如,如果每次獲取 Bean 時,都需要一個新的 Bean 例項,那麼應該將 Bean 的 scope 屬性定義為 prototype,如果 Spring 需要每次都返回一個相同的 Bean 例項,則應將 Bean 的 scope 屬性定義為 singleton。
Spring 5 共提供了 6 種 scope 作用域,如下表。
作用範圍 | 描述 |
---|---|
singleton | 預設值,單例模式,表示在 Spring 容器中只有一個 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 中有效。 |
注意:在以上 6 種 Bean 作用域中,除了 singleton 和 prototype 可以直接在常規的 Spring IoC 容器(例如 ClassPathXmlApplicationContext)中使用外,剩下的都只能在基於 Web 的 ApplicationContext 實現(例如 XmlWebApplicationContext)中才能使用,否則就會丟擲一個 IllegalStateException 的異常。
singleton(單例模式)
singleton 是 Spring 容器預設的作用域。當 Bean 的作用域為 singleton 時,Spring IoC 容器中只會存在一個共享的 Bean 例項。這個 Bean 例項將儲存在快取記憶體中,所有對於這個 Bean 的請求和引用,只要 id 與這個 Bean 定義相匹配,都會返回這個快取中的物件例項。
如果一個 Bean 定義的作用域為 singleton ,那麼這個 Bean 就被稱為 singleton bean。在 Spring IoC 容器中,singleton bean 是 Bean 的預設建立方式,可以更好地重用物件,節省重複建立物件的開銷。
Singleton 是單例型別,就是在建立起容器時就同時自動建立了一個 Bean 的物件,不管你是否使用,他都存在了,每次獲取到的物件都是同一個物件。注意,singleton 作用域是 Spring 中的預設作用域
在 Spring 配置檔案中,可以使用 <bean>
元素的 scope 屬性,將 Bean 的作用域定義成 singleton,其配置方式如下所示:<bean id="..." class="..." scope="singleton"/>
,原理圖如下所示:
prototype(原型模式)
如果一個 Bean 定義的作用域為 prototype,那麼這個 Bean 就被稱為 prototype bean。對於 prototype bean 來說,Spring 容器會在每次請求該 Bean 時,都建立一個新的 Bean 例項。
從某種意義上說,Spring IoC 容器對於 prototype bean 的作用就相當於 Java 的 new 操作符。它只負責 Bean 的建立,至於後續的生命週期管理則都是由客戶端程式碼完成的,詳情請參看《Spring Bean 生命週期》。
prototype 是原型型別,它在我們建立容器的時候並沒有例項化,而是當我們獲取 Bean 的時候才會去建立一個物件,而且我們每次獲取到的物件都不是同一個物件。根據經驗,對有狀態的 Bean 應該使用 prototype 作用域,而對無狀態的 Bean 則應該使用 singleton 作用域。
在 Spring 配置檔案中,可以使用 <bean>
元素的 scope 屬性將 Bean 的作用域定義成 prototype,其配置方式如下所示:<bean id="..." class="..." scope="prototype"/>
,原理圖如下所示:
2.2.4、Bean 的生命週期
Spring Bean 的生命週期指的是從一個普通的Java類變成Bean的過程。當一個 Bean 被例項化時,它可能需要執行一些初始化使它轉換成可用狀態。同樣,當 Bean 不再需要,並且從容器中移除時,可能需要做一些清除工作。
儘管還有一些在 Bean 例項化和銷燬之間發生的活動,但是本章將只討論兩個重要的生命週期回撥方法,它們在 Bean 的初始化和銷燬的時候是必需的。
為了定義安裝和拆卸一個 Bean,我們只要宣告帶有 init-method 和/或 destroy-method 引數的 Bean 即可。init-method 屬性指定一個方法,在例項化 Bean 時,立即呼叫該方法。同樣,destroy-method 指定一個方法,只有從容器中移除 Bean 之後,才能呼叫該方法。
Bean的生命週期可以簡單的表達為:例項化(Instantiation)->屬性賦值(Populate)->初始化(Initialization)->銷燬(Destruction)
接下來對 Bean 的整個生命週期做一個詳細的流程,其主要包括:例項化 Bean、依賴注入、注入 Aware 介面、BeanPostProcessor、InitializingBean 與 init-method 以及 DisposableBean 和 destroy-method。其實現例項化過程如下:
-
例項化 Bean:對於 BeanFactory 容器,當客戶向容器請求一個尚未初始化的 Bean 時,或初始化 Bean 的時候需要注入另一個尚未初始化的依賴時,容器就會呼叫 createBean 進行例項化。而對於 ApplicationContext 容器,當容器啟動結束後,便例項化所有的 Bean。容器通過獲取 BeanDefinition 物件中的資訊進行例項化,並且這一步僅僅是簡單的例項化,並未進行依賴注入。例項化物件被包裝在 BeanWrapper 物件中,BeanWrapper 提供了設定物件屬性的介面,從而避免了使用反射機制設定屬性。
-
依賴注入:例項化後的物件被封裝在 BeanWrapper 物件中,並且此時物件仍然是一個原生的狀態,並沒有進行依賴注入。緊接著,Spring 根據 BeanDefinition 中的資訊進行依賴注入,並且通過 BeanWrapper 提供的設定屬性的介面完成依賴注入。
-
注入 Aware 介面:緊接著,Spring 會檢測該物件是否實現了 xxxAware 介面,並將相關的 xxxAware 例項注入給Bean。
-
如果 Bean 實現了 BeanNameAware 介面,Spring 將 Bean 的名稱傳給 setBeanName() 方法;
-
如果 Bean 實現了 BeanFactoryAware 介面,Spring 將呼叫 setBeanFactory() 方法,將 BeanFactory 例項傳進來;
-
如果 Bean 實現了 ApplicationContextAware 介面,它的 setApplicationContext() 方法將被呼叫,將應用上下文的引用傳入到 Bean 中;
-
-
BeanPostProcessor:當經過上述幾個步驟後,Bean 物件已經被正確構造,但如果你想要物件被使用前再進行一些自定義的處理,就可以通過 BeanPostProcessor 介面實現。該介面提供了兩個函式:
-
postProcessBeforeInitialzation( Object bean, String beanName ):當前正在初始化的 Bean 物件會被傳遞進來,我們就可以對這個 Bean 作任何處理,這個函式會先於 InitialzationBean 執行,因此稱為前置處理。所有Aware介面的注入就是在這一步完成的。
-
postProcessAfterlnitialzation( Object bean, String beanName ):當前正在初始化的 Bean 物件會被傳遞進來,我們就可以對這個 Bean 作任何處理,這個函式會在 InitialzationBean 完成後執行,因此稱為後置處理。
-
-
InitializingBean 與 init-method:當 BeanPostProcessor 的前置處理完成後就會進入本階段。InitializingBean 介面只有一個函式:afterPropertiesSet()。
-
這一階段也可以在bean正式構造完成前增加我們自定義的邏輯,但它與前置處理不同,由於該函式並不會把當前 Bean 物件傳進來,因此在這一步沒辦法處理物件本身,只能增加一些額外的邏輯。若要使用它,我們需要讓 Bean 實現該介面,並把要增加的邏輯寫在該函式中,然後 Spring 會在前置處理完成後檢測當前 Bean 是否實現了該介面,並執行 afterPropertiesSet 函式。
-
當然,Spring 為了降低對客戶程式碼的侵入性,給 Bean 的配置提供了 init-method 屬性,該屬性指定了在這一階段需要執行的函式名。Spring 便會在初始化階段執行我們設定的函式。init-method 本質上仍然使用了 InitializingBean 介面。
-
-
DisposableBean 和 destroy-method:和 init-method 一樣,通過給 destroy-method 指定函式,就可以在 Bean 銷燬前執行指定的邏輯。若 Bean 實現了 DisposableBean 介面,Spring 將呼叫它的 distroy() 介面方法。同樣的,如果 Bean 使用了 destroy-method 屬性宣告瞭銷燬方法,則該方法被呼叫;
這裡特別說明一下 Awar e介面,Spring 的依賴注入最大亮點就是所有的 Bean 對 Spring 容器的存在是沒有意識的。但是在實際專案中,我們有時不可避免的要用到 Spring 容器本身提供的資源,這時候要讓 Bean 主動意識到 Spring 容器的存在,才能呼叫 Spring 所提供的資源,這就是 Spring 的 Aware 介面。
Aware 介面是個標記介面,標記這一類介面是用來“感知”屬性的,Aware 的眾多子介面則是表徵了具體要“感知”什麼屬性。例如 BeanNameAware 介面用於“感知”自己的名稱,ApplicationContextAware 介面用於“感知”自己所處的上下文。
其實 Spring 的 Aware 介面是 Spring設 計為框架內部使用的,在大多數情況下,我們不需要使用任何 Awar e介面,除非我們真的需要它們,實現了這些介面會使應用層程式碼耦合到 Spring 框架程式碼中。
接下來我們通過一個例子來演示一下 Spring Bean 的生命週期。首先通過在實體類中定義兩個方法,一個初始化方法和一個銷燬方法,然後在 xml 配置檔案中,對 Bean 進行裝配,並且通過 init-method="初始化方法" destroy-method="銷燬方法"
兩個屬性指定我們自定義的初始化和銷燬方法,實現程式碼如下:
public class SpringBeanSmzq {
private String massage;
public void init() {
System.out.println("初始化Bean");
}
public void destroy() {
System.out.println("銷燬Bean");
}
public String getMassage() {
return massage;
}
public void setMassage(String massage) {
this.massage = massage;
}
}
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="smzq" class="com.loner.mj.pojo.SpringBeanSmzq"
init-method="init" destroy-method="destroy">
<property name="massage" value="測試Spring Bean的生命週期" />
</bean>
</beans>
測試程式程式如下,最終輸出結果為:初始化Bean -> 測試Spring Bean的生命週期 -> 銷燬Bean
public class SpringBeanSmzqTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("smzq.xml");
SpringBeanSmzq smzq = (SpringBeanSmzq) applicationContext.getBean("smzq");
System.out.println(smzq.getMassage());
// 呼叫銷燬方法
applicationContext.registerShutdownHook();
}
}