1. 程式人生 > >Spring3.X學習筆記-IoC容器概述

Spring3.X學習筆記-IoC容器概述

2017即將接近尾聲,每當年末的時候總要檢查下今年還有什麼事情沒做,結果卻發現今年好像也沒做多少事情。於是就想著把之前一直想梳理的Spring知識,趁著年末好好整理下。本系列筆記基於”Spring3.x企業應用開發實戰“一書,說來慚愧,書買了好多年了也沒認真的看一遍,於是就有了這個想法,算是2017年的最後給自己的一份交代。

在開始之前,先簡要介紹下Spring吧!

Spring是分層的Java SE/EE應用一站式的輕量級開源框架,由Rod Johnson建立,以IoC和AOP為核心,提供了展現層Spring MVC和持久層Spring JDBC以及業務層事務管理等眾多企業級應用技術,並以海納百川的胸懷整合了開源世界裡眾多的企業級應用技術,逐漸成為使用最多的Java EE企業應用開發框架。

1、IoC概述

1.1 IoC的概念

IoC(控制反轉:Inverse of Control)是一個重要的面向物件程式設計理論,Spring核心模組實現了IoC的功能。Spring中的其他模組,像AOP、宣告式事務等功能都是建立在IoC的基礎之上,它將類和類之間的依賴從程式碼中脫離出來,用配置的方式進行依賴關係描述,由IoC容器負責依賴類之間的建立、拼接、管理、獲取等工作。一般來說IoC的概念有兩種表示方式,一個叫控制反轉,一個叫依賴注入。由於控制反轉並不好理解,業界也曾進行廣泛的討論,最終軟體界的泰斗級人物Martin Folwer提出了DI(依賴注入:Dependency Injection)的概念用以代替IoC。

控制反轉:對於軟體來說,即某一介面具體實現類的選擇控制權從呼叫類中移除,轉交給第三方決定。

依賴注入:呼叫類對某一介面實現類的依賴關係由第三方(容器或協作類)注入,以移除呼叫類對某一介面實現類的依賴。

上面兩個概念第一次接觸的時候,你會發現都不太好理解,那是因為還不清楚IoC的實現機制,隨著對IoC瞭解的深入,你會發現依賴注入的概念直接明瞭。

1.2 IoC的型別

從注入方法上看,主要可以劃分為三種類型:建構函式注入、屬性注入和介面注入。Spring支援建構函式注入和屬性注入。

  • 建構函式注入: 在建構函式注入中,我們通過呼叫類的建構函式,將介面實現類通過建構函式變數傳入。
  • 屬性注入: 屬性注入是指通過Setter方法完成呼叫類所需依賴的注入。
  • 介面注入: 將呼叫類所有依賴注入的方法抽取到一個介面中,呼叫類通過實現該介面提供相應的注入方法。

由於通過介面注入需要額外宣告一個介面,增加了類的數目,而且它的效果和屬性注入並無本質區別,所以不提倡採用這種方式。

2、IoC的底層實現原理

Spring的核心模組實現了IoC的功能,它通過配置檔案或註解描述類和類之間的依賴關係,自動完成類的初始化和依賴注入的工作。讓開發者們從底層實現類的例項化、依賴關係裝配等工作中脫離出來,專注於更有意義的業務邏輯開發工作。這種“神奇”的力量歸功於Java語言本身的類反射功能。

2.1 Java反射知識

Java語言允許通過程式化的方式間接對Class的物件例項操作,Class檔案由類裝載器裝載後,在JVM中將形成一份描述CLass結構的元資訊物件,通過該元資訊物件可以獲知Class的結構資訊:如建構函式、屬性和方法等。Java允許使用者藉由這個Class相關的元資訊物件間接呼叫Class物件的功能,這就為使用程式化方式操作Class物件開闢了途徑。下面先看一個例子:

public class Car {
    private String brand;
    private String color;
    private int maxSpeed;

    public Car() {}

    public Car(String brand, String color, int maxSpeed) {
        this.brand = brand;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }

    public void introduce() {
        System.out.println("brand:" + brand + ";color:" + color + ";maxSpeed:" + maxSpeed);
    }

    // 省略引數的getter/Setter方法
}

public class ReflectTest {

    public static Car initByDefaultConst() throws Throwable {
        // 通過類裝載器獲取Car類物件
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?> clazz = loader.loadClass("com.hhxs.bbt.web.Car");
        // 獲取類的預設構造器物件並通過它例項化Car
        Constructor<?> cons = clazz.getDeclaredConstructor((Class[]) null);
        Car car = (Car) cons.newInstance();
        // 通過反射方法設定屬性
        Method setBrand = clazz.getMethod("setBrand", String.class);
        setBrand.invoke(car, "紅旗CA72");
        Method setColor = clazz.getMethod("setColor", String.class);
        setColor.invoke(car, "黑色");
        Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class);
        setMaxSpeed.invoke(car, 200);
        return car;
    }

    public static void main(String[] args) throws Throwable {
        Car car1 = initByDefaultConst();
        car1.introduce();
    }
}


通過檢視執行結果,可以看到這和直接通過建構函式和方法呼叫類功能的效果是一致的,只不過前者是間接呼叫,後者是直接呼叫罷了。這說明我們完全可以通過程式設計方式呼叫Class的各項功能。如果我們將這些資訊以一個配置檔案的方式提供,就可以使用Java語言的反射功能編寫一段通用程式碼對類似於Car的類進行例項化及功能呼叫操作了。有沒有感覺這和上面提到的Spring框架的實現機制很相似,Spring正是基於Java語言自帶的反射機制實現了IoC的功能。

下面簡要介紹下上述例子用到得三個主要反射類,這些反射物件類在java.reflect包中定義:

  • Constructor: 類的建構函式反射類,通過Class#getConstructors()方法可以獲得類的所有建構函式反射物件陣列。Constructor的一個主要方法是newInstance(Object[]… initargs),通過該方法可以建立一個物件類的例項,相當new關鍵字。
  • Method: 類方法的反射類,通過Class#getDeclaredMethods()方法可以獲取類的所有方法發射類物件陣列Method[]。Method最主要的方法是invoke(Object obj, Objcet… args),obj表示操作的目標物件,args為方法入參。
  • Field: 類的成員變數反射類,通過Class#getDeclaredFields()方法可以獲取類的成員變數反射物件陣列。Filed類最主要的方法是set(Object obj, Object value),obj表示操作的目標物件,通過value為目標類物件的成員變數設定值。

此外,Java還為包提供了Package反射類,在JDK5.0中還未註解提供了AnnotatedElement反射類。總之,Java的反射體系保證了可以通過程式化的方式訪問目標類中的所有元素,對於private或protected的成員變數和方法,只要JVM的安全機制允許,也可以通過反射進行呼叫setAccessible(boolean access)

3、三個核心介面

Spring通過一個配置檔案描述Bean及Bean之間的依賴關係,利用Java語言的反射功能例項化Bean並建立Bean之間的依賴關係。Spring的IoC容器在完成這些底層工作的基礎上,還提供了Bean例項快取、宣告週期管理、Bean例項代理、事件釋出、資源裝載等高階服務。

3.1 BeanFactory

Bean工廠(com.springframework.beans.factory.BeanFactory)是Spring框架最核心的介面,它提供了高階IoC的配置機制。BeanFactory使管理不同不同型別的Java物件成為可能,一般稱BeanFactory為IoC容器。BeanFactory是類的通用工廠,它可以建立並管理各種類的物件,Spring稱這些被建立和管理的Java物件為Bean。Bean最主要的方法就是getBean(String beanName),該方法從容器中返回特定名稱的Bean。下面看一個小例子:

// spring檔案
<?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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="car" class="com.hhxs.bbt.web.Car"
        p:brand="紅旗CA72"
        p:color="黑色"
        p:maxSpeed="200" />
</beans>

// JAVA程式碼
public class BeanFactoryTest {
    public static void main(String[] args) throws Throwable{
       ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
       Resource res = resolver.getResource("classpath:spring/beans.xml");
       System.out.println(res.getURL());
       BeanFactory bf = new XmlBeanFactory(res);
       System.out.println("init BeanFactory.");

       Car car = bf.getBean("car",Car.class);
       System.out.println("car bean is ready for use!");
       car.introduce();
    }
}


注意: 通過BeanFactory啟動IoC容器時,並不會初始化配置檔案中定義的Bean,初始化動作發生在第一次呼叫時。對於單例項的Bean來說,BeanFactory會快取Bean例項,所以第二次使用getBean()獲取Bean時將直接從IoC容器的快取中獲取Bean例項。Spring在DefaultSingletonBeanRegistry類中提供了一個用於快取單例項Bean的快取器,它是一個用HashMap實現的快取器,單例項的Bean以beanName為鍵儲存在這個HashMap中。

3.2 ApplicationContext

應用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory基礎之上,提供了更多面嚮應用的功能,它提供了國際化支援和框架事件體系,更易於建立實際應用。一般稱ApplicationContext為應用上下文或者Spring容器。

ApplicationContext的主要實現類是ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,前者預設從類路徑中載入配置檔案,後者預設從檔案系統中裝載配置檔案。

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/beans.xml");    
//或者
ApplicationContext ctx = new FileSystemXmlApplicationContext("spring/beans.xml");


在獲取ApplicationContext例項後,就可以像BeanFactory一樣呼叫getBean(beanName)返回Bean了。需要注意的是ApplicationContext在初始化應用上下文時就例項化所有單例項的Bean。因此,相比BeanFactory,初始化時間也會相對較長些,不過之後的呼叫就不在有“第一次懲罰”的問題。

3.3 WebApplicationContext

WebApplicationContext是專門為Web應用準備的,它允許應用從相對於Web根目錄的路徑中裝載配置檔案完成初始化工作。從WebApplicationContext中可以獲得ServletContext的引用,整個Web應用上下文物件將作為屬性放置到ServletContext中,以便Web應用環境可以訪問Spring應用上下文。

Spring與Wen應用的上下文融合

WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所區別,因為WebApplicationContext需要ServletContext例項,也就是說它必須在擁有Web容器的前提下才能完成啟動的工作。有過Web開發經驗的讀者都知道可以在web.xml中配置自啟動的Servlet(spring3.0及以後版本中已經刪除)或定義Web容器監聽器(ServletContextListener),藉助這兩者中的任何一個都可以完成啟動Spring Web應用上下文的工作。

通過web容器監聽器啟動:web.xml

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    classpath:/spring/spring-context.xml
  </param-value>
</context-param>

<!-- spring容器啟動監聽器 -->
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

4、Bean的生命週期

4.1 BeanFactory中Bean的生命週期

我們知道Web容器中的Servlet擁有明確的生命週期,Spring容器中的Bean也擁有相似的生命週期。我們可以從兩個層面定義Bean的生命週期:第一個層面是Bean的作用範圍;第二個層面是例項化Bean時所經歷的一系列階段。

BeanFactory中Bean的生命週期

1. 當呼叫者通過getBean(beanName)向容器請求某一個Bean時,如果容器註冊了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor介面,在例項化Bean之前,將呼叫介面的postProcessBeforeInstantiation()方法;
2. 根據配置情況呼叫Bean建構函式或工廠方法例項化Bean;
3. 如果容器註冊了InstantiationAwareBeanPostProcessor介面,在例項化Bean之後,呼叫該介面的postProcessAfterInstantiationn()方法,可以在這裡對已經例項化的物件進行處理。
4. 如果Bean配置了屬性資訊,容器在這一步著手將配置值設定到Bean對應的屬性中,不過在設定每個屬性值之前先呼叫InstantiationAwareBeanPostProcessor介面的postProcessPropertyValues()方法;
5. 呼叫Bean的屬性設定方法設定屬性值;
6. 如果Bean實現了org.springframework.beans.factory.BeanNameAware介面,將呼叫setBeanName()介面方法,將配置檔案中該Bean對應的名稱設定到Bean中;
7. 如果Bean實現了org.springframework.bean.factory.BeanFactoryAware介面,將呼叫setBeanFactory()介面方法,將BeanFactory容器例項設定到Bean中;
8. 如果BeanFactory裝配了org.springframework.beans.factory.config.BeanPostProcessor後處理器,將呼叫BeanPostProcessor的Object postProcessBeforeInstantiation(Object bean, Stringn beanName)介面方法對Bean進行加工操作。其中入參bean是當前正在處理的Bean,而beanName是當前Bean的配置名,返回的物件為加工處理後的Bean。BeanPostProcessor在Spring框架中佔有重要的地位,為容器提供duiBean進行後續架構處理的切入點,Spring容器所提供的各種“神奇功能”(如AOP,動態代理等)都通過BeanPostProcessor實施
9. 如果Bean實現了InitializingBean的介面,將呼叫該介面的afterPropertiesSet()方法;
10. 如果在通過init-method屬性定義了初始化方法,將執行這個方法;
11. BeanPostProcessor後處理定義了兩個方法:其一時postProcessBeforeInstantiation()在第8步呼叫;其二是Object postProcessAfterInstantiationn(Object bean, String beanName)方法,這個方法在此時呼叫,容器中再次獲得對Bean進行加工處理的機會。
12. 如果在中指定Bean的作用範圍是scope=”prototype”,將Bean返回給呼叫者,呼叫者負責Bean後續生命的管理,Spring不再管理這個Bean的生命週期。如果作用範圍設定為scope=“singleton”,則將Bean放入到Spring IoC容器的快取池中,並將Bean引用返回給呼叫者,Spring繼續對這些Bean進行後續的生命管理。
13. 對於scope=“singleton”的Bean,當容器關閉時,將觸發Spring對Bean的後續生命週期的管理工作,首先如果Bean實現了DisposableBean介面,則將呼叫介面的afterPropertiesSet()方法,可以在此編寫釋放資源、記錄日誌等操作。
14. 對於scope=“singleton”的Bean,如果通過的destroy-method屬性指定了Bean的銷燬方法,Spring將執行Bean的這個方法,完成Bean資源的釋放等操作。

Bean生命週期例項:

public class Car implements BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean {
    private String brand;
    private String color;
    private int maxSpeed;

    private BeanFactory beanFactory;
    private String beanName;

    public Car() {
        System.out.println("呼叫Car()建構函式。");
    }

    public Car(String brand, String color, int maxSpeed) {
        this.brand = brand;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }

    public void introduce() {
        System.out.println("brand:" + brand + ";color:" + color + ";maxSpeed:" + maxSpeed);
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        System.out.println("呼叫setBrand()設定屬性。");
        this.brand = brand;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
    // 5 DisposableBean方法
    @Override
    public void destroy() throws Exception {
        System.out.println("呼叫DisposaleBean.destroy()。");
    }
    // 4 IntializingBean介面方法
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("呼叫IntializingBean.afterPropertiesSet()。");
    }
    // 3 BeanNameAware介面方法
    @Override
    public void setBeanName(String name) {
        System.out.println("呼叫BeanNameAware.setBeanName()。");
        this.beanName = beanName;
    }
    // 2 BeanFactoryAware介面方法
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("呼叫BeanFactoryAware.setBeanFactory()。");
        this.beanFactory = beanFactory;
    }
    // 6 通過<bean>的init-method屬性指定的初始化方法
    public void myInit() {
        System.out.println("呼叫init-method所指定的myInit(),將maxSpeed設定為240。");
        this.maxSpeed = 240;
    }
    // 7 通過<bean>的destroy-method屬性指定的銷燬方法
    public void myDestroy() {
        System.out.println("呼叫destroy-method所指定的myDestroy()。");
    }
}

public class MyInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {

    // 1 在例項化Bean前進行呼叫
    public Object postProcessBeforeInitialization(Class beanClass, String beanName) throws BeansException {
        // 1-1 僅對容器中Car Bean進行處理
        if ("car".equals(beanName)) {
            System.out.println("InstantiationAware BeanPostProcessor.postProcessBeforeInstantiation");
        }
        return null;
    }

    // 2 在例項化Bean後呼叫
    public boolean postProcessAfterInstantiation(Object bean, String beanName) {
        // 2-1 僅對容器中Car Bean進行處理
        if ("car".equals(beanName)) {
            System.out.println("InstantiationAware BeanPostProcessor.postProcessAfterInstantiation");
        }
        return true;
    }

    // 3 在設定某個屬性時呼叫
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean,
            String beanName) throws BeansException {
        // 3-1 僅對容器中Car Bean進行處理,還可以通過pdst入參進行過濾,僅對car的某個特定屬性時進行處理
        if("car".equals(beanName)) {
            System.out.println("InstantiationAware AwareBeanPostProcessor.postProcessPropertyValues");
        }
        return pvs;
    }
}

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        if(beanName.equals("car")) {
            Car car = (Car)bean;
            if(car.getColor() == null) {
                System.out.println("呼叫BeanPostProcessor.postProcessBeforeInitialization(),color為空,設定為預設黑色。");
                car.setColor("黑色");
            }
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("car")) {
            Car car = (Car)bean;
            if(car.getMaxSpeed() >= 200) {
                System.out.println("呼叫BeanPostProcessor.postProcess AfterInitialization(), 將maxSpeed調整為200。");
                car.setMaxSpeed(200);
            }
        }
        return bean;
    }

}

**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" xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="car" class="com.hhxs.bbt.web.Car"
        init-method="myInit"
        destroy-method="myDestroy"
        p:brand="紅旗CA72"
        p:maxSpeed="200"
        scope="singleton" />
</beans>

public class BeanLifeCycle {

    private static void lifeCycleInBeanFactory() {

        Resource res = new ClassPathResource("spring/beans.xml");
        BeanFactory bf = new XmlBeanFactory(res);

        // 向容器中註冊後處理器
        ((ConfigurableBeanFactory)bf).addBeanPostProcessor(new MyBeanPostProcessor());
        ((ConfigurableBeanFactory)bf).addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor());

        // 第一次從容器中獲取Car,將觸發容器例項化該Bean,這將引發Bean生命週期方法的呼叫。
        Car car1 = (Car)bf.getBean("car");
        car1.introduce();
        car1.setColor("紅色");
        car1.introduce();

        // 第二次從容器中獲取Car,直接從快取池中獲取
        Car car2 = (Car)bf.getBean("car");

        // 檢視car1和car2是否指向同一引用
        System.out.println("car1==car2:" + (car1==car2));

        // 關閉容器
        ((XmlBeanFactory)bf).destroySingletons();
    }

    public static void main(String[] args) {
        lifeCycleInBeanFactory();
    }
}


執行上述程式碼,我們在控制檯上得到以下輸出資訊,仔細觀察,將發現它驗證了我們前面所介紹的Bean生命週期過程。

呼叫Car()建構函式。
InstantiationAware BeanPostProcessor.postProcessAfterInstantiation
InstantiationAware AwareBeanPostProcessor.postProcessPropertyValues
呼叫setBrand()設定屬性。
呼叫BeanNameAware.setBeanName()。
呼叫BeanFactoryAware.setBeanFactory()。
呼叫BeanPostProcessor.postProcessBeforeInitialization(),color為空,設定為預設黑色。
呼叫IntializingBean.afterPropertiesSet()。
呼叫init-method所指定的myInit(),將maxSpeed設定為240。
呼叫BeanPostProcessor.postProcess AfterInitialization(), 將maxSpeed調整為200。
brand:紅旗CA72;color:黑色;maxSpeed:200
brand:紅旗CA72;color:紅色;maxSpeed:200
car1==car2:true
呼叫DisposaleBean.destroy()。
呼叫destroy-method所指定的myDestroy()。

4.2 ApplicationContext中Bean的生命週期

Bean在應用上下文中的生命週期和BeanFactory中生命週期類似,不同的是,如果Bean實現org.springframework.context.ApplicationContextAware介面,會增加一個呼叫該介面方法setApplicationContext()的步驟。

ApplicationContext中Bean的生命週期

此外,如果配置檔案中聲明瞭工作後處理器介面BeanFactoryPostProcessor的實現類,則應用上下文在裝載配置檔案之後初始化Bean例項之前將呼叫這些BeanFactoryPostProcessor對配置資訊進行加工處理。工廠後處理器是容器級的,僅在應用上下文初始化時呼叫一次,其目的是完成一些配置檔案的加工處理工作。

ApplicationContext和BeanFactory另一最大的不同之處在於:前者會利用Java反射機制自動識別出配置檔案中定義的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor,並自動將它們註冊到應用上下文中;而後者需要在程式碼中拿你給通過手工呼叫addBeanPostProcessor()方法進行註冊。這也是為什麼在應用開發時,我們普遍使用ApplicationContext而很少使用BeanFactory的原因之一。

————本文結束感謝您的閱讀————