Spring學習筆記(二) IoC容器與Bean

本文爲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 容器的所有功能,所以通常建議使用 ApplicationContextBeanFactory 仍然可以用於輕量級的應用程序,如移動設備或基於 applet 的應用程序,其中它的數據量和速度是顯著。

BeanFactory 容器

在Spring中,有大量對BeanFactory接口的實現。其中,最常見的是 XmlBeanFactory 類。這個容器從一個 XML 文件中讀取配置元數據,由這些元數據來生成一個被配置化的系統或者應用。

筆記(一) 中的 HelloWorld實例 爲例。
HelloWorld.javaBeans.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-methoddefault-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 有兩個屬性 message1message2 。然後使用 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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章