1. 程式人生 > 其它 >Spring原理與原始碼分析系列(三)- Spring IoC容器啟動過程分析(下)

Spring原理與原始碼分析系列(三)- Spring IoC容器啟動過程分析(下)

前言

關於Spring容器啟動過程的分析,本章節文章分為兩篇文章進行敘述,第一篇主要介紹Spring中Bean的相關概念以及IoC容器型別;第二篇開始詳細介紹IoC容器的啟動過程。
上篇Spring原理與原始碼分析系列(二)- Spring IoC容器啟動過程分析(上)已經介紹了介紹Spring中Bean的相關概念以及IoC容器型別。本篇主要詳述IoC容器的啟動過程。

四、Spring IoC容器實現過程

Spring IoC容器實現過程可分為兩個階段:

  • 容器啟動階段
  • Bean例項化階段
下面來詳細解釋這兩個過程。

1、容器啟動階段

容器啟動階段,主要是物件管理資訊的收集。
除了直接程式碼的方式,一般是先讀取和載入配置資訊內容,
並將分析後的資訊編組為BeanDefinition,
然後將儲存了bean定義必要資訊的BeanDefinition註冊到BeanDefinitionRegistry中,這樣啟動工作就完成了。

 

(第一階段:容器啟動階段)

BeanFactoryPostProcessor
在容器啟動階段,BeanFactoryPostProcessor介面允許我們在容器例項化相應物件之前,對註冊到容器的BeanDefinition所儲存的資訊做相應的修改,比如修改其中bean定義的某些屬性,把bean的scope從singleton改為prototype,也可以把property的值給修改掉,為bean定義增加其他資訊等。
BeanFactoryPostProcessor是在Spring容器載入了bean的定義檔案之後,在bean例項化之前執行的。

BeanFactoryPostProcessor介面定義如下:

public interface BeanFactoryPostProcessor {

  void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

介面方法的入參是ConfigurrableListableBeanFactory,使用該引數,可以獲取到相關bean的定義資訊.

使用的時候,我們可以自己實現BeanFactoryPostProcessor介面,然後修改Bean屬性。 
舉個栗子: 
(1)Bean:

package com.wgs.spring.beanfactorypostprocessor;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/21.
 */
public class Staff {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

 併為其注入屬性值:

<bean id="staff" class="com.wgs.spring.beanfactorypostprocessor.Staff">
        <property name="age" value="25"></property>
        <property name="name" value="wgs"></property>
</bean>

 (2)自己實現一個BeanFactoryPostProcessor,將Bean屬性中的name原始值“wgs”改為“Jack Ma”。

package com.wgs.spring.beanfactorypostprocessor;

import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/21.
 */
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{


    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        //BeanFactoryPostProcessor發生在讀取Bean的BeanDefinition後,Bean例項化之前,所以獲取的是BeanDefinition
        BeanDefinition staffBeanDefinition = beanFactory.getBeanDefinition("staff");
        //獲取bean屬性
        MutablePropertyValues propertyValues = staffBeanDefinition.getPropertyValues();
        if(propertyValues.contains("name")){
            propertyValues.addPropertyValue("name", "Jack Ma");
        }
    }
}

 

3)測試:
註釋掉spirng.xml中的配置:
<bean id="myBeanFactoryPostProcessor" class="com.wgs.spring.beanfactorypostprocessor.MyBeanFactoryPostProcessor"></bean>,輸出的結果為”wgs”;

在spirng.xml加上<bean id="myBeanFactoryPostProcessor" class="com.wgs.spring.beanfactorypostprocessor.MyBeanFactoryPostProcessor"></bean>,這樣BeanFactoryPostProcessor就能起作用,使name的值“wgs”被修改為“Jack Ma”,所以輸出的結果也為“Jack Ma”。

如果一個容器有多個實現BeanFactoryPostProcessor的介面,這時候就需要實現類實現org.springframework.core.Ordered介面,設定order屬性來保證自定義的BeanFactoryPostProcessor的實現類的執行順序。

BeanFactoryPostProcessor介面有三個常用的實現類: 

 

  • org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:允許我們在XML配置檔案中使用佔位符,並將這些佔位符所代表的資源單獨配置到簡單的properties檔案中來載入;
  • org.springframework.beans.factory.config.PropertyOverrideConfigurer:可以通過佔位符,來明確表明bean定義中的property與properties檔案中的各項配置項之間的關係。
  • org.springframework.beans.factory.config.CustomEditorConfigurer:用來註冊自定義的屬性編輯器


BeanFactoryPostProcessor類圖: 

 

2 Bean例項化階段

在第一階段容器啟動階段中,所有的bean定義的資訊都被註冊到BeanDefinitionRegistry中,該階段容器僅僅擁有所有物件的BeanDefinition來儲存所有必要的例項化資訊。
當某個請求顯示或隱式呼叫getBean()方法的時候,

就會觸發第二階段:Bean例項化階段。

隱式呼叫有兩種情況: 
(1)BeanFactory: 
BeanFactory的物件例項化預設採用的是延遲初始化,即只有對某個Bean使用getBean()方法時,才會對該Bean進行例項化以及依賴注入過程,這是一個顯示呼叫過程。
如果物件A被請求而需要第一次例項化的時候,如果A依賴的物件B沒有被例項化,那麼容器內部會隱士呼叫getBean()方法對物件B進行例項化後,再進行顯示呼叫getBean()例項化物件A過程。
(2)ApplicationContext: ApplicationContext在容器啟動之後就會載入所有的bean定義。 
不過在這個過程中,是通過呼叫AbstractApplicationContext的refresh()方法,在這個方法中會呼叫註冊到容器當中所有的bean定義的例項化方法getBean(),完成Bean的例項化。

 

無論是顯示還是隱士getBean(),Bean定義的getBean()方法第一次被呼叫時才會觸發Bean例項化階段,若getBean()內部發現該Bean沒有被例項化,則會通過createBean()方法進行具體的例項化。第二次以及之後呼叫getBean(),都會直接返回容器內快取的Bean的例項(prototype型別的bean除外)。

下圖是Bean的例項化過程:

 

 

Bean的生命週期)

Bean例項化階段容器會首先檢查所有請求的物件之前是否已經初始化,如果沒有,則會根據註冊的BeanDefinition所提供的資訊例項化被請求物件,並未其注入依賴。如果該物件實現了某個某些回撥介面,也會根據回撥介面來裝配。當物件裝配完成以後,容器就會返回該bean。

下面是Bean例項化階段中幾個過程。

(1)Bean的例項化與BeanWrapper

Spring提供了兩種方式來例項化Bean:

  • 反射
  • CGLIB動態位元組碼生成
Spring採用策略模式選擇上述方式來例項化Bean。
org.springframework.beans.factory.support.InstantiationStrategy 是例項化策略的介面,其直接子類SimpleInstantiationStrategy實現了簡單的物件例項化功能。
CglibSubclassingInstantiationStrategy繼承了SimpleInstantiationStrategy,可通過CGLIB的動態位元組碼生成功能。
容器內部預設採用的是CglibSubclassingInstantiationStrategy

在Bean例項化完成後,返回的不是Bean例項,而是以BeanWrapper對構造完成的Bean進行包裝,返回BeanWrapper例項。

為什麼使用BeanWrapper對Bean進行包裝呢?
因為BeanWrapper對Bean例項操作很方便,可以免去直接使用Java反射API操作物件例項的繁瑣。

舉個栗子:
使用反射來操作Bean:

    @Test
    public void testReflect() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        //1 獲取物件例項
        Object commentService = Class.forName("com.wgs.spring.beanwrapperdemo.CommentService").newInstance();
        Object commentDao = Class.forName("com.wgs.spring.beanwrapperdemo.CommentDao").newInstance();
        //2 獲取屬性
        Class commentServiceClazz = commentService.getClass();
        Field commentDaoField = commentServiceClazz.getField("commentDao");
        //3 設定屬性
        commentDaoField.set(commentService, commentDao);

        System.out.println(((CommentService)commentService).getCount());
    }

 可以看到,不僅程式碼很繁瑣,需要獲取屬性再設值,還需要處理一堆的異常; 
而使用BeanWrapper:

 @Test
    public void testBeanWapper() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //1 獲取物件例項
        Object commentService = Class.forName("com.wgs.spring.beanwrapperdemo.CommentService").newInstance();
        Object commentDao = Class.forName("com.wgs.spring.beanwrapperdemo.CommentDao").newInstance();

        BeanWrapper commentServiceWrapper = new BeanWrapperImpl(commentService);
        commentServiceWrapper.setPropertyValue("commentDao", commentDao);

        System.out.println(((CommentService)commentService).getCount());
    }

 

獲取到BeanWrapper包裝的Bean後,只需一行程式碼就可以完成設定注入過程,是不是很簡單呢。

(2)Aware介面

當物件例項化完成且相關屬性即依賴值注入完成後,IoC容器會檢查當前物件例項是否實現了XXXAware介面。如果是,則將這些XXXAware介面定義中規定的依賴注入給當前物件例項。

BeanFactory有如下XXXAware介面:

org.springframework.beans.factory.BeanNameAware介面:如果當前物件例項實現了該介面,會將該物件例項的bean對應的beanName設定到當前物件例項;
org.springframework.beans.factory.BeanClassLoaderAware介面:如果當前物件例項實現了該介面,會將該物件例項的bean對應的ClassLoader注入到當前物件例項;
org.springframework.beans.factory.BeanFactoryAware介面:如果當前物件例項實現了該介面,BeanFactory容器會將自身設定到當前物件例項;這樣當前物件例項就擁有了一個BeanFactory容器的引用。

 ApplicationContext有如下XXXAware介面:

org.springframework.context.ResourceLoaderAware介面:ApplicationContext實現了ResourceLoader介面;若當前物件例項實現了ResourceLoaderAware介面,會將ApplicationContext設定到物件例項,這樣當前物件例項就獲取了ApplicationContext容器的引用;
org.springframework.context.ApplicationEventPublisherAware介面:ApplicationContext實現了ApplicationEventPublisher介面;若當前物件例項實現了ApplicationEventPublisherAware介面,會將ApplicationContext自身注入到當前物件例項;
org.springframework.context.MessageSourceAware介面:ApplicationContext實現了MessageSource介面;若當前物件例項實現了MessageSourceAware介面,會將ApplicationContext自身注入到當前物件例項;
org.springframework.context.ApplicationContextAware介面:若當前物件例項實現了ApplicationContextAware介面,會將ApplicationContext自身注入到當前物件例項(換句話說就是獲得ApplicationContext中的所有bean,即載入Spring上下文環境)。

 下面舉個栗子,通過ApplicationContextAware Demo來理解下XXXAware介面的作用: 
(1)首先註冊一個Bean:

package com.wgs.spring.xxxawaredemo;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/22.
 */
public class User {
    private String name;
    private String password;

    public void setPassword(String password) {
        this.password = password;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String getPassword() {
        return password;
    }
}


<bean id="user" class="com.wgs.spring.xxxawaredemo.User">
  <property name="password" value="12345"></property>
  <property name="name" value="wgs"></property>
</bean>

 (2)自己實現一個ApplicationContextAware介面的實現類,通過setApplicationContext方法將ApplicationContext上下文注入到當前物件例項,之後就可以在該物件中獲取ApplicationContext容器的bean的資訊:

package com.wgs.spring.xxxawaredemo;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/22.
 */
public class MyApplicationContextAware implements ApplicationContextAware{


    private ApplicationContext context;

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    public void doGetInfo(){
    //獲取到ApplicationContext容器當中的Bean的資訊
        User userBean = (User) context.getBean("user");
        System.out.println("登入使用者姓名:" + userBean.getName());
        System.out.println("登入密碼:" + userBean.getPassword());
    }
}

XML中配置:
 <bean id="myApplicationContextAware" class="com.wgs.spring.xxxawaredemo.MyApplicationContextAware"></bean>

 (3)測試,獲取結果

package com.wgs.spring.xxxawaredemo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/22.
 */
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration({"classpath:spring.xml"})
public class TestMyApplicationContextAware {

    @Autowired
    MyApplicationContextAware myApplicationContextAware;

    @Test
    public void testApplicationContextAware(){
        myApplicationContextAware.doGetInfo();
    }
}

 

測試後,即可獲取Bean的相關資訊。

(3)BeanPostProcessor

上節容器啟動階段說過BeanFactoryPostProcessor這個後置處理器,本節將會了解下處理器BeanPostProcessor。兩者有所相似,區別在於發生的階段不同,

BeanFactoryPostProcessor:存在於容器啟動階段,可以修改Bean屬性; 
BeanPostProcessor:存在於Bean例項化階段,在Bean例項化完成後增加一些自己的邏輯。

 BeanPostProcessor會處理容器內符合條件的例項化後的物件例項。該物件聲明瞭兩個方法:

public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object var1, String var2) throws BeansException;

Object postProcessAfterInitialization(Object var1, String var2) throws BeansException;
}

 

postProcessBeforeInitialization()是BeanPostProcessor前置處理執行方法,
postProcessAfterInitialization()是BeanPostProcessor後 置處理執行方法。
BeanPostProcessor的兩個方法中都傳入了原來物件的例項的引用,這樣就可以對傳入的物件進行操作。

下面舉個栗子簡單感受下BeanPostProcessor的用法:
(1)註冊一個Bean:

package com.wgs.spring.beanpostprocessordemo;

import org.springframework.beans.factory.InitializingBean;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/22.
 */
public class User{
    private String name;
    private String password;

    public void setPassword(String password) {
        this.password = password;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String getPassword() {
        return password;
    }

}

<bean id="user" class="com.wgs.spring.beanpostprocessordemo.User">
 </bean>

 (2)自己寫一個BeanPostProcessor介面的實現類,在Bean例項化前後加入自己的邏輯:

package com.wgs.spring.beanpostprocessordemo;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/22.
 */
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + "開始例項化了");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + "例項化完成");
        return bean;
    }
}

 (3)測試:

package com.wgs.spring.beanpostprocessordemo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/22.
 */
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration({"classpath:spring.xml"})
public class TestMyBeanPostProcessor {

    @Test
    public void testMyBeanPostProcessor(){
        //BeanFactory測試
       ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring.xml"));
        beanFactory.addBeanPostProcessor(new MyBeanPostProcessor());
        User user = (User) beanFactory.getBean("user");
        //ApplicationContext測試
        //ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:spring.xml");
    }
}

注意,如果是ApplicationContext容器,則只需要在XML配置下MyBeanPostProcessor即可:
<bean id="myBeanPostProcessor" class="com.wgs.spring.beanpostprocessordemo.MyBeanPostProcessor"/>

 

測試結果:
user開始例項化了
user例項化完成

(4)InitializingBean 和 init-method

org.springframework.beans.factory.InitializingBean是容器內部廣泛使用的一個物件生命週期標識介面,其作用在於在物件例項化過程中呼叫BeanPostProcessor的前置處理器postProcessBeforeInitialization後,會接著檢測當前物件是否實現了InitializingBean介面。如果實現了該介面,則會呼叫afterPropertiesSet()方法,對物件做進一步處理。

該介面定義如下:

public interface InitializingBean {

  void afterPropertiesSet() throws Exception;

}

當我們在物件中實現該介面,就可以通過afterPropertiesSet方法來完成初始化操作。但該方法對於容器比較具有侵入性,所以Spring提供了另外一種方法:在XML中使用 < bean>的init-method屬性。

舉個栗子來看下InitializingBean 和 init-method的使用方法:
(1)當Login類中的name和password為Null時,通過實現InitializingBean 介面,在afterPropertiesSet方法中為其附上初始值:

package com.wgs.spring.beanpostprocessordemo;

import org.springframework.beans.factory.InitializingBean;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/23.
 */
public class Login implements InitializingBean {
    private String name;
    private String password;

    public void setName(String name) {
        this.name = name;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public String getPassword() {
        return password;
    }

    public void doGetInfo(){
        System.out.println(name);
        System.out.println(password);
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        if(null == name || "".equals(name)){
            name = "Admin";
        }
        if(null == password ||  "".equals(password)){
            password = "12345";
        }
    }
}

 XML中配置,不賦值:

<bean id="login" class="com.wgs.spring.beanpostprocessordemo.Login"></bean>

 (2)測試:

package com.wgs.spring.beanpostprocessordemo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/23.
 */
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration({"classpath:spring.xml"})
public class TestInitializingBean {

    @Test
    public void testInitializingBean(){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring.xml");
        Login login = (Login) ctx.getBean("login");
        login.doGetInfo();
    }
}

 

輸出結果: 
Admin 
12345

使用init-method: 
Login 類不變,只是將afterPropertiesSet方法改為initMethod()方法:

package com.wgs.spring.beanpostprocessordemo;

import org.springframework.beans.factory.InitializingBean;

/**
 * @author GenshenWang.nomico
 * @date 2017/11/23.
 */
public class Login {
    private String name;
    private String password;

    public void setName(String name) {
        this.name = name;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public String getPassword() {
        return password;
    }

    public void doGetInfo(){
        System.out.println(name);
        System.out.println(password);
    }
    public void initMethod() {
        if(null == name || "".equals(name)){
            name = "Admin";
        }
        if(null == password ||  "".equals(password)){
            password = "12345";
        }
    }
}

 XML中配置:

 <bean id="login" class="com.wgs.spring.beanpostprocessordemo.Login" init-method="initMethod"></bean>

 

總結:
1、Spring為bean提供了兩種初始化bean的方式:

  • 實現現InitializingBean介面,重寫afterPropertiesSet方法,
  • 在XML配置檔案中通過init-method指定初始化方法;
2、實現InitializingBean介面是直接呼叫afterPropertiesSet方法,比通過反射呼叫init-method指定的方法效率相對來說要高點。但是init-method方式消除了對spring的依賴

3、如果呼叫afterPropertiesSet方法時出錯,則不呼叫init-method指定的方法。

(5)DisposableBean 和 destroy-method

與InitializingBean介面類似,當Bean實現了DisposableBean 介面或者在XML中聲明瞭destroy-method的指定方法,就會為該例項註冊一個用於物件銷燬的回撥方法。在Spring容器關閉的時候,需要我們告知容器來執行物件的銷燬方法。

BeanFactory容器
我們需要在合適的時機,呼叫ConfigurableListableBeanFactory的destroySingletons()方法來銷燬容器中管理的所有singleton型別的物件例項;

 BeanFactory container = new XmlBeanFactory(new ClassPathResource("classpath:spring.xml"));
        ((ConfigurableListableBeanFactory)container).destroySingletons();
//應用程式退出,容器關閉

 

ApplicationContext容器
類似BeanFactory,但是AbstractApplicationContext為我們提供了registerShutdownhook()方法,該方法底層使用標準的Runtime類的addShutdownHook()方式來呼叫相應bean物件的銷燬邏輯,從而保證在JVM退出之前,這些singleton型別的bean物件例項自定義銷燬邏輯會被執行。

 BeanFactory container = new  ClassPathXmlApplicationContext("classpath:spring.xml");
        ((AbstractApplicationContext)container).registerShutdownHook();
//應用程式退出,容器關閉

 至此,Bean的生命就結束了,Spring Ioc容器啟動過程分析也到此結束,接下來我會從原始碼的角度來深入分析這個過程。