1. 程式人生 > >Spring(二):瞭解IOC

Spring(二):瞭解IOC

IOC(Inversion of Control):其思想是反轉資源的獲取方向。控制反轉,使程式元件或類之間儘量形成一種鬆耦合的結構。開發者在使用類的例項之前,需要先建立物件的例項。但IOC將建立例項的任務交給IOC容器。這樣,開發者使用程式碼時只需要直接使用類的例項。下來講下IOC的一些基礎知識。

<bean>元素

  Spring的bean的定義和bean相互間的依賴關係是通過配置XML檔案中的元資料來實現的。Spring IOC容器管理一個或多個bean。這些bean將通過配置檔案中的bean定義被建立(在XML格式中為<bean/>元素)
 <bean>元素屬性說明:
  • id:代表JavaBean的例項物件。在JavaBean例項化後可以通過id來引用其例項物件(必須是唯一的,沒有指定,Spring自動將許可權定性類名作為Bean的名
  • name:代表JavaBean的例項物件名
  • class:JavaBean的類名(包含路徑),元素的必選屬性
  • singleton:是否使用單例
  • autowire:Spring的JavaBean的自動裝配功能
  • init-method:指定JavaBean的初始化方法 
  • destory-method:指定JavaBean被回收之前呼叫的銷燬方法
  • depends-on:使用者保證在depends-on指定的JavaBean被例項化之前,再例項化自身JavaBean

BeanFactory

   BeanFactory是Spring的“心臟”,使用BeanFactory來例項化,配置和管理Bean。是IOC容器的核心介面,定義了IOC的基本功能,主要定義了getBean方法,genBean方法是IOC容器獲取bean物件和引發依賴注入的起點。方法的功能是返回特定名稱的Bean
  BeanFactory有眾多的子介面,實現類。
  BeanFactory的原始碼:
package org.springframework.beans.factory;
public interface BeanFactory {
    /**
     * 用來引用一個例項,或把它和工廠產生的Bean區分開,就是說,如果一個FactoryBean的名字為a,那麼,&a會得到那個Factory
     */
    String FACTORY_BEAN_PREFIX = "&";
    /*
     * 四個不同形式的getBean方法,獲取例項
     */
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    Object getBean(String name, Object... args) throws BeansException;
    boolean containsBean(String name); // 是否存在
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否為單例項
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否為原型(多例項)
    boolean isTypeMatch(String name, Class<?> targetType)
            throws NoSuchBeanDefinitionException;// 名稱、型別是否匹配
    Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 獲取型別
    String[] getAliases(String name);// 根據例項的名字獲取例項的別名
  從BeanFactory原始碼可看出:
  • 4個獲取例項的方法:getBean的過載方法
  • 4個判斷方法。判斷是否存在,是否為單例,原型,名稱是否匹配
  • 1個獲取型別的方法,一個獲取別名的的方法。根據名稱獲取型別,根據名稱獲取別名

BeanFactory最常見的實現類是XMLBeanFactory,可以從classpath或檔案資源等獲取資源。

(1)File file = new File("fileSystemConfig.xml");
Resource resource = new FileSystemResource(file);
BeanFactory beanFactory = new XmlBeanFactory(resource);
(2)
Resource resource = new ClassPathResource("classpath.xml"); 
BeanFactory beanFactory = new XmlBeanFactory(resource);

ApplicationContext

  ApplicationContext是由BeanFactory派生而來的,提供了更多面向實際應用大的功能。在BeanFactory中,很多功能需要以程式設計的方式實現,而在ApplicationContext中可以通過配置實現。
  BeanFactory介面提供了配置框架及基本功能,但無法支援Sprig的AOP功能和Web應用。而ApplicationContext實現了BeanFactory的所有功能,同時還做了擴充套件
  • MessageSource,提供了國際化的訊息訪問 
  • 資源訪問,例如URL和檔案
  • 事件傳播特性,即支援AOP特性。
  • 載入多個(有繼承關係)上下文,使得每一個上下文都專注於一個特定的層次,例如web層
  ApplicationContext介面有三個實現類,可以例項化其中任何一個類來建立Spring的ApplicationContext容器。
 1. ClassPathXmlApplicationContext類
  ClassPathXmlApplicationContext是從當前路徑中檢索配置檔案並裝載它來建立容器的例項。具體語法格式如下:
ApplicationContext context=new ClassPathXmlApplicationContext(String configLocation)
configLocation引數指定了Spring配置檔案的名稱和位置
  2.FileSystemXMLApplicationContext
   FileSystemXMLApplicationContext通過引數指定配置方式的位置,可以獲取類路徑之外的資源。具體語法格式如下:
ApplicationContext context=new FileSystemXmlAppplicationContext(String configLocation)
 3.WebApplicationContext工作
   WebApplicationContext是Spring的Web應用容器(是專門為Web應用準備的,允許從相對於Web根目錄的路徑中完成初始化),有兩種方法可以在Servlet中使用。
  • 在Servlet的web.xml檔案中配置Spring的ContextLoaderListener監聽器
  • 修改web.xml檔案,在配置檔案中新增一個Servlet,定義使用Spring的org.springframework.web.context.ContextLoaderServlet類

BeanFactory與ApplicationContext的區別

  1.丟擲異常的不同
   BeanFactory採用的是延遲載入的形式注入Bean,即只有在使用到某個Bean時(呼叫getBean()),才對該Bean進行載入例項化。如果Bean的某一個屬性沒有注入,BeanFactory載入後,直到第一次呼叫getBean方法才會丟擲異常。所以不能及時發現配置問題
   ApplicationContext則相反,它是在容器啟動時,一次性建立所有Bean。則在初始化自身時檢驗,這樣有利於檢查所依賴屬性是否注入。所以在容器啟動時,就可以發現Spring的配置錯誤
   相對於基本的BeanFactory,ApplicationContext唯一不足的是佔用記憶體空間。當應用程式配置Bean較多時,程式啟動較慢。
2.對Bean的處理
    BeanFactory和ApplicationContext都支援BeanPostProcessor、BeanFactoryPostProcessor的使用,但兩者之間的區別是:BeanFactory需要手動註冊,而ApplicationContext則是自動註冊。(Applicationcontext比 beanFactory 加入了一些更好使用的功能。而且 beanFactory 的許多功能需要通過程式設計實現而 Applicationcontext 可以通過配置實現。比如後處理 bean , Applicationcontext 直接配置在配置檔案即可而 beanFactory 這要在程式碼中顯示的寫出來才可以被容器識別。 )
3.beanFactory主要是面對與 spring 框架的基礎設施,面對 spring 自己。而 Applicationcontex 主要面對與 spring 使用的開發者。基本都會使用 Applicationcontex 並非 beanFactory 。

依賴注入

 1.屬性注入
   基於JavaBean的Setter方法為屬性賦值。因為一個簡單的JavaBean最明顯的規則就是一個私有屬性對應的setter和getter方法,來實現對屬性的封裝。既然JavaBean有Setter方法設定bean的屬性,Spring就會有相應的支援。在 配置檔案applicationContext.xml中的<property>元素可以為JavaBean的Setter方法傳參,即通過Setter方法為屬性賦值。
<property>屬性說明:
  • name:指定Bean的屬性名稱
  • value(<value>子節點):指定屬性值
  2.構造器注入
   在類被例項化時,它的構造方法被呼叫並且只能呼叫一次。
   < constructor-arg>是<bean>元素的子元素。可以通過<constructor-arg>的<value>子元素可以為構造方法傳參。
   如果標籤的賦值順序與構造方法中的引數的順序或引數型別不同,程式會產生異常。 可以使用<constructor-arg>元素的index和type屬性來解決
  • index:用於指定構造方法的引數索引,指定當前<constructor-arg>標籤為構造方法的哪個引數賦值
  • type:可以指定引數型別以確定要為構造方法的哪個引數賦值,當需要賦值的屬性在構造方法中沒有相同的型別,可以使用這個引數。

3.工廠方法注入

注入細節

  1.集合屬性
  當注入的屬性是集合屬性,在Spring中可以通過一組內建的xml標籤(例如:<list>,<set>或<map>)來配置集合屬性。陣列定義和List一樣,都使用<list>
  <list>標籤中:
  • <ref>:指定對其他<bean>的引用
  • <bean>:指定內建Bean定義
  • <null>:指定空元素(為Bean的字串或其它物件型別的屬性注入null值)
<map>標籤中:
  • <entry>:作為子標籤,每個條目包含一個鍵和一個值(Map的鍵值)
  • <key>:定義鍵
  • <value>:定義鍵值
 2.引入其他的bean
   在Spring中可以通過配置檔案使用<ref>元素引用其他JavaBean的例項物件
 3.內部Bean
   當Bean例項僅給一個特性的屬性使用時,可以將其宣告為內部Bean。內部Bean宣告直接包含在<property>或<constructor-arg>元素裡,不需要設定任何id或name屬性。內部Bean不能使用在任何其他地方

自動裝配

   <bean>元素的 autowire屬性負責自動裝配<bean>標籤定義JavaBean的屬性。
屬性取值有:
  • byname:以屬性名區分自動裝配。在容器中尋找與JavaBean屬性名相同的JavaBean,並將其自動裝配到JavaBean中。(如果存在名稱相同但型別不同的JavaBean,會出現錯誤裝配JavaBean的可能)
  • byType:尋找與JavaBean的屬性型別相同的JavaBean的定義,並將其自動裝配到JavaBean中。(如果存在多個與目標Bean型別一致的Bean,Spring將無法判定哪個Bean最合適該屬性,會丟擲一個org.beans.factory.UnsatisfiedDependencyException異常)
  • no:採用預設值,採用自動裝配。必須使用ref直接使用其他bean。可以增加程式碼的可讀性,並不易出錯
  • constructor:通過構造方法的引數型別自動裝配。此型別會使容器自動尋找與JavaBean的構造方法的引數型別相同的bean,並注入到需要自動裝配的JavaBean中。它與byType型別存在相同的無法識別自動裝配的情況
  • autodetect:首先使用constructor方式來自動裝配,然後使用byType方式。它與byType和constructor型別存在相同的無法識別自動裝配的情況
  自動裝配的優點:能顯著減少配置的數量。可以使配置與Java程式碼同步更新。
  自動裝配的缺點:會出現裝配不明確的情況,丟擲異常。
  一般情況下,在實際專案中很少使用自動裝配功能,而是使用明確清晰的配置文件。

bean的作用域

  在Spring中,可以在<bean>元素中scope屬性裡設定Bean的作用域
  1.singleton作用域:
     預設情況下,Spring只為每個在IOC容器裡宣告Bean建立唯一一個例項,整個IOC容器範圍都能共享該例項。所有後續的getBean()呼叫和Bean引用都將返回這個唯一的Bean例項,該作用域被稱為singleton。
    作用域為singleton的bean生命週期與Spring IOC容器是一致的,該bean在容器初始化時被建立,然後被一直保留到容器內。當容器銷燬後,bean也將被銷燬。
    設定bean作用域為singleton,有三種方式:
<bean id="..." class="..."/> //預設
<bean id="..." class="..." singleton="true"/> 
<bean id="..." class="..." scope="singleton"/>
  2.prototype作用域
    bean會導致在每次對該bean請求(將其注入到另一個bean中,或呼叫getbean方法)時都會建立一個新的bean例項。但是在prototype作用域中當bean被容器建立完畢,並且將例項物件返回給請求方之後,容器就不再有當前返回物件的引用,容器將例項物件的生命週期管理工作交給請求方負責,所以在客戶端程式碼中必須使用bean的後置處理器清除prototype作用域的bean。後置清理器中持有要被清理的bean的引用。
   設定bean作用域為prototype,有兩種方式:
<bean id="..." class="..." singleton="false"/> 
<bean id="..." class="..." scope="prototype"/>
  3.request
   每次HTTP請求都會建立一個新的Bean,該作用域僅適用於WebApplicationContext環境
  4.session
   同一個HTTP Session共享一個Bean,不同的HTTP Session使用不同的Bean。該作用域僅適用於WebApplicationContext環境

IOC容器中bean的生命週期

   BeanFactory中的bean生命週期分為例項化,初始化,使用和銷燬四個階段。
  1.Spring IOC容器對bean的生命週期進行管理的過程:
      (1).通過構造器或工廠方法建立bean例項
      (2).為bean的屬性設定值和對其他bean的引用
      (3).呼叫bean的初始化方法
      (4).bean可以使用了
      (5).當容器關閉時,呼叫bean的銷燬方法
   在bean的聲明裡設定init-method和destroy-method屬性,為bean定義初始化和銷燬方法
 2.bean的初始化(容器會按照JavaBean的定義初始化bean的所有屬性和依賴關係):

    (1).在bean的定義中,如果<bean>標籤使用了autowire屬性,Spring會對bean完成自動裝配
    (2).通過get和set方法配置bean的屬性
    (3).如果bean實現了BeanNameAware介面,容器將會呼叫bean的setBeanName()方法來傳遞bean的ID
    (4).如果bean實現了BeanFactoryAware介面,容器將會呼叫bean的setBeanFactory()方法將容器本身 注入到JavaBean中
    (5).如果在容器中註冊了BeanPostProcessor介面的實現類,將呼叫這個實現類的postProcessBeforeInitialization()方法,完成bean的預處理方法
    (6).如果bean實現了InitializingBean介面,容器會呼叫bean的afterPropertiesSet()方法修改bean屬性
    (7).在XML中配置bean時,如果用init-method屬性指定了初始化方法,那麼容器會執行指定的方法來設定屬性
   (8).最後,容器中如果註冊了BeanPostProcessor的實現類,將呼叫實現類postProcessAfterInitialization()方法完成bean的後期處理方法
 3.新增bean後置處理器後bean的生命週期
  在bean初始化時,如果註冊BeanPostProcessor的實現類,就是添加了bean的後置處理器。新增後,生命週期進行管理的過程如下:
   (1).通過構造器或工廠方法建立bean例項
   (2).為bean的屬性設定值和對其他bean的引用
   (3).將bean例項傳遞給bean後置處理器的postProcessBeforeInitialization()方法
   (4).呼叫bean的初始化方法
   (5).將bean例項傳遞給bean後置處理器的postProcessAfterInitialization()方法
   (6).bean可以使用了
   (7).當容器關閉時,呼叫bean的銷燬方法
 4.bean的銷燬
   (1).當關閉容器時,容器會銷燬所有的bean,但是如果bean定製了特殊的銷燬方法,容器會在銷燬該bean之前呼叫這個方法完成資源回收等操作。詳細說明如下:
   (2).在銷燬bean之前如果bean實現了DisposableBean介面,那麼容器會呼叫bean的destroy()方法來完成銷燬前的工作。例如,在bean銷燬之前對其資料庫連線關閉,檔案資料流關閉等
   (3).如果在bean的定義資訊中定義JavaBean的銷燬方法,那麼bean被銷燬前就先去執行指定的方法。如果同時實現了步驟(1)的介面,會先去執行destroy()方法。即DisposableBean介面優先於bean的定義。

BeanFactoryPostProcessor和BeanPostProcessor

BeanFactoryPostProcerssor介面原始碼:
public interface BeanFactoryPostProcessor { 

/ ** 
*修改應用程式上下文的內部bean工廠的標準 
*初始化。所有bean定義都將被載入,但沒有bean 
*將被例項化。這允許覆蓋或新增 
*屬性即使是急切地初始化bean。 
* @param beanFactory是bean工廠使用的應用程式上下文 
* @throws org.springframework.beans.BeansException在出現錯誤的情況下 
* / 
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; 

} 
   實現該介面,可以在Spring的bean被建立之前,可以修改bean的定義屬性。Spring允許BeanFactoryPostProcessor在容器例項化任何bean之前讀取配置元資料,並可以根據需要進行修改(例如:可以將bean的scope的prototype改成singleton)。可以同時配置多個BeanFactoryPostProcessor,並通過設定order屬性來控制各個執行順序
注意:BeanFactoryPostProcessor是Spring容器載入bean的定義檔案之後,在bean例項化之前執行的。介面方法的入參是ConfigurrableListableBeanFactory,使用該引數,可以獲取到相關的bean的定義資訊
   BeanPostProcessor:該介面的作用是:如果我們需要在Spring容器完成Bean的例項化、配置和其他的初始化前後新增一些自己的邏輯處理,我們就可以定義一個或者多個   BeanPostProcessor介面的實現,然後註冊到容器中。
   BeanPostProcessor介面有兩個方法需要實現:postProcessBeforeInitialization和postProcessAfterInitialization
介面定義如下:
public interface BeanPostProcessor { 
/** 
* Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean 
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet} 
* or a custom init-method). The bean will already be populated with property values. 
*/ 
//例項化、依賴注入完畢,在呼叫顯示的初始化之前完成一些定製的初始化任務 
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; 
/** 
* Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean 
* initialization callbacks (like InitializingBean's {@code afterPropertiesSet} 
* or a custom init-method). The bean will already be populated with property values. 
*/ 
//例項化、依賴注入、初始化完畢時執行 
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; 
}