本文爲Spring的學習筆記,有參考極客學院的wiki內容。
(我的博客原文地址:https://chaycao.github.io/2016/09/19/StudyNotesOfSpring2/ 請多指教!)
IoC容器
Spring容器是Spring框架的核心。容器將創建對象,把它們連接在一起,配置它們,並管理他們的整個生命週期從創建到銷燬。
Spring容器使用依賴注入(DI)來管理組成一個應用程序的組件。這些對象被稱爲 Spring Beans。
通過閱讀配置元數據提供的指令,容器知道對哪些對象進行實例化,配置和組裝。
配置元數據的三種表示方式: XML,Java 註釋、 Java 代碼
下圖是 Spring 如何工作的高級視圖。 Spring IoC 容器利用 Java 的 POJO 類和配置元數據來生成完全配置和可執行的系統或應用程序。
Spring提供了兩種不同類型的容器
- Spring BeanFactory 容器
最簡單的容器,給DI提供了基本支持,由org.springframework.beans.factory.BeanFactory接口來定義。
- Spring ApplicationContext 容器
該容器添加了更多的企業特定的功能,例如從一個屬性文件中解析文本信息的能力,發佈應用程序事件給感興趣的事件監聽器的能力。該容器是由 org.springframework.context.ApplicationContext 接口定義。
ApplicationContext 容器包括 BeanFactory 容器的所有功能,所以通常建議使用 ApplicationContext。 BeanFactory 仍然可以用於輕量級的應用程序,如移動設備或基於 applet 的應用程序,其中它的數據量和速度是顯著。
BeanFactory 容器
在Spring中,有大量對BeanFactory接口的實現。其中,最常見的是 XmlBeanFactory 類。這個容器從一個 XML 文件中讀取配置元數據,由這些元數據來生成一個被配置化的系統或者應用。
以 筆記(一) 中的 HelloWorld實例 爲例。
HelloWorld.java 和 Beans.xml 內容不變
下面是MainApp.java的內容:
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class MainApp {
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory
(new ClassPathResource("Beans.xml"));
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
obj.getMessage();
}
}
- 第一步利用框架提供的 XmlBeanFactory() API 去生成工廠 bean 以及利用 ClassPathResource() API 去加載在路徑 CLASSPATH 下可用的 bean 配置文件。XmlBeanFactory() API 負責創建並初始化所有的對象,即在配置文件中提到的 bean。
- 第二步利用第一步生成的 bean 工廠對象的 getBean() 方法得到所需要的 bean。 這個方法通過配置文件中的 bean ID 來返回一個真正的對象,該對象最後可以用於實際的對象。一旦得到這個對象,就可以利用這個對象來調用任何方法。
Spring ApplicationContext容器
最常被使用的 ApplicationContext 接口實現:
- FileSystemXmlApplicationContext:該容器從 XML 文件中加載已被定義的 bean。在這裏,你需要提供給構造器* XML 文件的完整路徑*
- ClassPathXmlApplicationContext:該容器從 XML 文件中加載已被定義的 bean。在這裏,你不需要提供 XML 文件的完整路徑,只需正確配置 CLASSPATH 環境變量即可,因爲,容器會從 CLASSPATH 中搜索 bean 配置文件。
- WebXmlApplicationContext:該容器會在一個* web 應用程序的範圍內*加載在 XML 文件中已被定義的 bean。
在 筆記(一) 中的 HelloWorld實例 中,我們已經使用過了 ClassPathXmlApplicationContext 容器。 WebXmlApplicationContext將在後面的章節使用,這裏寫一個 FileSystemXmlApplicationContext 的例子:
HelloWorld.java 和 Beans.xml 的內容不變
下面是 MainApp.java 的內容:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext
("C:/Users/ZARA/workspace/HelloSpring/src/Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
- 第一步生成工廠對象。加載完指定路徑下 bean 配置文件後,利用框架提供的 FileSystemXmlApplicationContext API 去生成工廠bean。FileSystemXmlApplicationContext 負責生成和初始化所有的對象,比如,所有在 XML bean 配置文件中的 bean。
- 第二步利用第一步生成的上下文中的 getBean() 方法得到所需要的 bean。 這個方法通過配置文件中的 bean ID 來返回一個真正的對象。一旦得到這個對象,就可以利用這個對象來調用任何方法。
Bean 定義
bean 是一個被實例化,組裝,並通過 Spring IoC 容器所管理的對象。這些 bean 是由用容器提供的配置元數據創建的。
配置元數據具有以下信息:
- 如何創建一個 bean
- bean 的生命週期的詳細信息
- bean 的依賴關係
上述所有的配置元數據轉換成一組構成每個 bean 定義的下列屬性。
- class : 強制的,指定用來創建 bean 的 bean 類
- name : 指定唯一的 bean 標識符。在基於 XML 的配置元數據中,可用ID或name屬性指定 bean 標識符
- scope : 指定由特定的 bean 定義創建的對象的作用域
- constructor-arg : 用來注入依賴關係,後面討論
- properties : 用來注入依賴關係,後面討論
- autowiring mode : 用來注入依賴關係,後面討論
- lazy-initialization mode : 延遲初始化 bean
- initialization 方法 : 在 bean 的所有必須的屬性被容器設置後,調用回調方法
- destruction 方法 : 當包含該 bean 的容器被銷燬時,調用回調方法
下面給出一個基於 XML 配置文件的例子,給出不同的 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">
<!-- A simple bean definition -->
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- A bean definition with lazy init set on -->
<bean id="..." class="..." lazy-init="true">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- A bean definition with initialization method -->
<bean id="..." class="..." init-method="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- A bean definition with destruction method -->
<bean id="..." class="..." destroy-method="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
</beans>
Bean 作用域
Spring 框架支持以下五個作用域 :
1. singleton : 將 bean 的定義限制在每一個 Spring IoC容器中的一個單一實例(默認)
2. prototype : 將單一 bean 的定義限制在任意數量的對象實例。
3. request : 將 bean 的定義限制爲 HTTP 請求。只在 web-aware Spring ApplicationContext 的上下文中有效。
4. session : 將 bean 的定義限制爲 HTTP 會話。只在 web-aware Spring ApplicationContext 的上下文中有效。
5. global-session : 將 bean 的定義限制爲全局 HTTP 會話。只在 web-aware Spring ApplicationContext 的上下文中有效。
本章只討論前兩個,後三個當討論到 web-aware Spring ApplicationContext 的時候討論。
singleton 作用域
如果作用域爲 singleton,那麼 Spring IoC容器中只能創建一個由該 bean 定義的對象的實例。
該單一實例將存儲在這種單例 bean 的高速緩存中,以及針對該 bean 的所有後續的請求和引用都返回緩存對象。
默認作用域始終是 singleton ,也可以在 bean 的配置文件中配置:
<!-- A bean definition with singleton scope -->
<bean id="..." class="..." scope="singleton">
<!-- collaborators and configuration for this bean go here -->
</bean>
下面看一個例子,MainApp.java的內容如下
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld");
objA.setMessage("I'm object A");
objA.getMessage();
HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
objB.getMessage();
}
}
輸出結果如下:
Your Message : I'm object A
Your Message : I'm object A
prototype 作用域
如果作用域設置爲 prototype,那麼每次特定的 bean 發出請求時 Spring IoC 容器就創建對象的新的 Bean 實例。
一般,有狀態的 bean 使用 prototype 作用域,無狀態的 bean 使用 singleton 作用域。
可以在 XML 配置文件中設置作用域爲 prototype
<!-- A bean definition with singleton scope -->
<bean id="..." class="..." scope="prototype">
<!-- collaborators and configuration for this bean go here -->
</bean>
下面給出一個例子,MainApp.java文件內容如下:
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("I'm object A");
objA.getMessage();
HelloWorld objB = (HelloWorld) context.getBean("helloWorld");
objB.getMessage();
}
}
輸出:
Your Message : I'm object A
Your Message : null
Bean 生命週期
當一個bean被實例化時,可能需要執行一些初始化使它轉換成可用狀態。當bean不再需要,並且從容器中移除時,可能做一些清楚工作。
本章只討論兩個重要的生命週期回調方法,它們在bean的初始化和銷燬是必須的。
初始化回調
第一種方式:實現相應的接口
org.springframework.beans.factory.InitializingBean 接口指定一個單一的方法:
void afterPropertiesSet() throws Exception;
通過實現上述接口,初始化工作就可以在 afterPropertiesSet() 方法中執行,如下所示:
public class ExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
第二種方式:在基於 XML 的配置元數據中, init-method 屬性指定帶有void無參數方法的名稱。例如:
<bean id="exampleBean"
class="examples.ExampleBean" init-method="init"/>
下面是類的定義:
public class ExampleBean {
public void init() {
// do some initialization work
}
}
銷燬回調
第一種方式:實現相應的接口
org.springframework.beans.factory.DisposableBean 接口指定一個單一的方法:
void destroy() throws Exception;
通過實現上述接口,結束工作就可以在 destroy() 方法中執行,如下所示:
public class ExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work
}
}
第二種方式:在基於 XML 的配置元數據中, destory-method 屬性指定帶有void無參數方法的名稱。例如:
<bean id="exampleBean"
class="examples.ExampleBean" destroy-method="destroy"/>
下面是類的定義:
public class ExampleBean {
public void destroy() {
// do some destruction work
}
}
如果在非 web 應用程序環境中使用 Spring 的 IoC 容器(例如豐富的客戶端桌面環境),那麼在 JVM 中要註冊關閉hook,確保正常關閉,讓所有資源被釋放,可以在單個 bean 上調用 destory 方法。
建議:使用第二種方式,因爲 XML 配置在命名方法上具有極大的靈活性。
默認的初始化和銷燬方法
如果有太多具有相同名稱的初始化或者銷燬方法的 Bean,那麼不需要在 bean 上聲明初始化方法和銷燬方法。框架使用元素中 default-init-method 和 default-destory-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.xsd"
default-init-method="init"
default-destroy-method="destroy">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
</beans>
Bean 後置處理器
BeanPostProcessor 接口定義回調方法,你可以在 Spring 容器通過插入一個或多個 BeanPostProcessor 的實現來完成實例化,配置和初始化一個bean之後實現一些自定義邏輯回調方法。
當有多個 BeanPostProcessor 實現類時,可通過設置 BeanPostProcessor 接口實現的 Ordered 接口提供的 order 屬性來控制這些 BeanPostProcessor 實現類的執行順序。
ApplicationContext 會自動檢測由 BeanPostProcessor 接口的實現定義的 bean,註冊這些 bean 爲後置處理器,然後通過在容器中創建 bean,在適當的時候調用它。
下面是一個例子:
這裏是 HelloWorld.java 的內容:
public class HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public void getMessage(){
System.out.println("Your Message : " + message);
}
public void init(){
System.out.println("Bean is going through init.");
}
public void destroy(){
System.out.println("Bean will destroy now.");
}
}
下面 InitHelloWorld 實現了 BeanPostProcessor 接口,並且實現接口定義的兩個方法 postProcessBeforeInitialization() postProcessAfterInitialization(),第一個參數爲bean的Object對象,第二個參數爲bean的名稱。當初始化任意一個bean前,回調第一個函數。初始化任意一個bean後,回調第二個函數。
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InitHelloWorld implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeforeInitialization : " + beanName);
return bean; // you can return any other object as well
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("AfterInitialization : " + beanName);
return bean; // you can return any other object as well
}
}
下面是 MainApp.java 的內容。註冊一個在 AbstractApplicationContext 類中聲明的關閉 hook 的 registerShutdownHook() 方法,確保正常關閉,並且調用相關的 destory 方法。
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();
}
}
下面是 init 和 destory 方法需要的配置文件 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.xsd">
<bean id="helloWorld" class="com.chaycao.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="message" value="Hello World!"/>
</bean>
<bean class="com.chaycao.InitHelloWorld" />
</beans>
輸出:
BeforeInitialization : helloWorld
Bean is going through init.
AfterInitialization : helloWorld
Your Message : Hello World!
Bean will destroy now.
Bean 定義繼承
子 bean 的定義繼承父定義的配置數據。子定義可以根據需要重寫一些值,或者添加其他值。
Spring Bean 定義的繼承與 Java 類的繼承無關,但是繼承的概念是一樣的。可以定義一個父 bean 的定義作爲模板和其他子 bean 就可以從父 bean 中繼承所需的配置。
例子:
下面是配置文件 Beans.xml ,“helloworld” bean 有兩個屬性 message1 和 message2 。然後使用 parent 屬性把 “helloIndia” bean 定義爲 “helloWorld” bean 的孩子,並繼承它的屬性,同時自己還添加一個屬性 message3。
<?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="helloWorld" class="com.chaycao.HelloWorld">
<property name="message1" value="Hello World!"/>
<property name="message2" value="Hello Second World!"/>
</bean>
<bean id="helloIndia" class="com.chaycao.HelloIndia" parent="helloWorld">
<property name="message1" value="Hello India!"/>
<property name="message3" value="Namaste India!"/>
</bean>
</beans>
下面是 HelloWorld.java 的內容:
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);
}
}
下面是 HelloIndia.java 的內容:
public class HelloIndia {
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("India Message1 : " + message1);
}
public void getMessage2(){
System.out.println("India Message2 : " + message2);
}
public void getMessage3(){
System.out.println("India Message3 : " + message3);
}
下面是 MainApp.java 的內容:
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.getMessage1();
objA.getMessage2();
HelloIndia objB = (HelloIndia) context.getBean("helloIndia");
objB.getMessage1();
objB.getMessage2();
objB.getMessage3();
}
}
輸出:
World Message1 : Hello World!
World Message2 : Hello Second World!
India Message1 : Hello India!
India Message2 : Hello Second World!
India Message3 : Namaste India!
在創建 “helloIndia” bean 的同時並沒有傳遞 message2,但由於 Bean 定義的繼承,所以傳遞了 message2。
Bean 定義模板
在定義一個 Bean 定義模板時,不指定class的屬性,將abstract的屬性置爲 true,如下所示:
<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="beanTeamplate" abstract="true">
<property name="message1" value="Hello World!"/>
<property name="message2" value="Hello Second World!"/>
<property name="message3" value="Namaste India!"/>
</bean>
<bean id="helloIndia" class="com.chaycao.HelloIndia" parent="beanTeamplate">
<property name="message1" value="Hello India!"/>
<property name="message3" value="Namaste India!"/>
</bean>
</beans>