1. 程式人生 > >spring載入bean流程解析

spring載入bean流程解析

spring作為目前我們開發的基礎框架,每天的開發工作基本和他形影不離,作為管理bean的最經典、優秀的框架,它的複雜程度往往令人望而卻步。不過作為朝夕相處的框架,我們必須得明白一個問題就是spring是如何載入bean的,我們常在開發中使用的註解比如@Component、@AutoWired、@Socpe等註解,Spring是如何解析的,明白這些原理將有助於我們更深刻的理解spring。需要說明一點的是spring的原始碼非常精密、複雜,限於篇幅的關係,本篇部落格不會細緻的分析原始碼,會採取抽絲剝繭的方式,避輕就重,抓住重點來分析整個流程(不會分析具體的細節),本次將會基於spring5.0的版本

本篇部落格的目錄:

一:spring讀取配置或註解的過程

二:spring的bean的生命週期

三:spring的BeanPostProcessor處理器

四:一些關鍵性的問題

五:測試

六:總結 

一:spring讀取配置或註解的過程 

1:先通過掃描指定包路徑下的spring註解,比如@Component、@Service、@Lazy @Sope等spring識別的註解或者是xml配置的屬性(通過讀取流,解析成Document,Document)然後spring會解析這些屬性,將這些屬性封裝到BeanDefintaion這個介面的實現類中.

在springboot中,我們也可以採用註解配置的方式:

 比如這個配置Bean,spring也會將className、scope、lazy等這些屬性裝配到PersonAction對應的BeanDefintaion中.具體採用的是BeanDefinitionParser介面中的parse(Element element, ParserContext parserContext)方法,該介面有很多不同的實現類。通過實現類去解析註解或者xml然後放到BeanDefination中,BeanDefintaion的作用是集成了我們的配置物件中的各種屬性,重要的有這個bean的ClassName,還有是否是Singleton、物件的屬性和值等(如果是單例的話,後面會將這個單例物件放入到spring的單例池中)。spring後期如果需要這些屬性就會直接從它中獲取。然後,再註冊到一個ConcurrentHashMap中,在spring中具體的方法就是registerBeanDefinition(),這個Map存的key是物件的名字,比如Person這個物件,它的名字就是person,值是BeanDefination,它位於DefaultListableBeanFactory類下面的beanDefinitionMap類屬性中,同時將所有的bean的名字放入到beanDefinitionNames這個list中,目的就是方便取beanName;

二:spring的bean的生命週期

spring的bean生命週期其實最核心的分為4個步驟,只要理清三個關鍵的步驟,其他的只是在這三個細節中新增不同的細節實現,也就是spring的bean生明週期:

例項化和初始化的區別:例項化是在jvm的堆中建立了這個物件例項,此時它只是一個空的物件,所有的屬性為null。而初始化的過程就是講物件依賴的一些屬性進行賦值之後,呼叫某些方法來開啟一些預設載入。比如spring中配置的資料庫屬性Bean,在初始化的時候就會將這些屬性填充,比如driver、jdbcurl等,然後初始化連線

2.1:例項化 Instantiation

     AbstractAutowireCapableBeanFactory.doCreateBean中會呼叫createBeanInstance()方法,該階段主要是從beanDefinitionMap迴圈讀取bean,獲取它的屬性,然後利用反射(core包下有ReflectionUtil會先強行將構造方法setAccessible(true))讀取物件的構造方法(spring會自動判斷是否是有引數還是無引數,以及構造方法中的引數是否可用),然後再去建立例項(newInstance)

2.2:初始化

   初始化主要包括兩個步驟,一個是屬性填充,另一個就是具體的初始化過程

2.2.1:屬性賦值 PopulateBean()會對bean的依賴屬性進行填充,@AutoWired註解注入的屬性就發生這個階段,假如我們的bean有很多依賴的物件,那麼spring會依次呼叫這些依賴的物件進行例項化,注意這裡可能會有迴圈依賴的問題。後面我們會講到spring是如何解決迴圈依賴的問題

2.2:初始化 Initialization

       初始化的過程包括將初始化好的bean放入到spring的快取中、填充我們預設的屬性進一步做後置處理等
3: 使用和銷燬 Destruction

      在Spring將所有的bean都初始化好之後,我們的業務系統就可以呼叫了。而銷燬主要的操作是銷燬bean,主要是伴隨著spring容器的關閉,此時會將spring的bean移除容器之中。此後spring的生命週期到這一步徹底結束,不再接受spring的管理和約束。

三:spring的BeanPostProcessor處理器

spring的另一個強大之處就是允許開發者自定義擴充套件bean的初始化過程,最主要的實現思路就是通過BeanPostProcessor來實現的,spring有各種前置和後置處理器,這些處理器滲透在bean建立的前前後後,穿插在spring生命週期的各個階段,每一步都會影響著spring的bean載入過程。接下來我們就來分析具體的過程:

 

 3.1:例項化階段

該階段會呼叫物件的空構造方法進行物件的例項化,在進行例項化之後,會呼叫InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法

BeanPostProcessor(具體實現是InstantiationAwareBeanPostProcessor). postProcessBeforeInstantiation();

這個階段允許在Bena進行例項化之前,允許開發者自定義邏輯,如返回一個代理物件。不過需要注意的是假如在這個階段返回了一個不為null的例項,spring就會中斷後續的過程。
BeanPostProcessor.postProcessAfterInstantiation();

這個階段是Bean例項化完畢後執行的後處理操作,所有在初始化邏輯、裝配邏輯之前執行

3.2:初始化階段

3.2.1:BeanPostProcessor.postProcessBeforeInitialization

該方法在bean初始化方法前被呼叫,Spring AOP的底層處理也是通過實現BeanPostProcessor來執行代理邏輯的

3.2.2:InitializingBean.afterPropertiesSet

自定義屬性值 該方法允許我們進行對物件中的屬性進行設定,假如在某些業務中,一個物件的某些屬性為null,但是不能顯示為null,比如顯示0或者其他的固定數值,我們就可以在這個方法實現中將null值轉換為特定的值

3.2.3:BeanPostProcessor.postProcessAfterInitialization(Object bean, String beanName)。可以在這個方法中進行bean的例項化之後的處理,比如我們的自定義註解,對依賴物件的版本控制自動路由切換。比如有一個服務依賴了兩種版本的實現,我們如何實現自動切換呢?這時候可以自定義一個路由註解,假如叫@RouteAnnotaion,然後實現BeanPostProcessor介面,在其中通過反射拿到自定義的註解@RouteAnnotaion再進行路由規則的設定。


3.2.4:SmartInitializingSingleton.afterSingletonsInstantiated

4.1:容器啟動執行階段

4.1.1:SmartLifecycle.start

容器正式渲染完畢,開始啟動階段,bean已經在spring容器的管理下,程式可以隨時呼叫

5.1:容器停止銷燬
5.1.1:SmartLifecycle.stop(Runnable callback) 

spring容器停止執行
5.1.2:DisposableBean.destroy()

spring會將所有的bean銷燬,實現的bean例項被銷燬的時候釋放資源被呼叫

四:一些關鍵性的問題

4.1:FactoryBean和BeanFactory的區別?

BeanFactory是個bean 工廠類介面,是負責生產和管理bean的工廠,是IOC容器最底層和基礎的介面,spring用它來管理和裝配普通bean的IOC容器,它有多種實現,比如AnnotationConfigApplicationContext、XmlWebApplicationContext等。

FactoryBean是FactoryBean屬於spring的一個bean,在IOC容器的基礎上給Bean的實現加上了一個簡單工廠模式和裝飾模式,是一個可以生產物件和裝飾物件的工廠bean,由spring管理,生產的物件是由getObject()方法決定的。注意:它是泛型的,只能固定生產某一類物件,而不像BeanFactory那樣可以生產多種型別的Bean。在對於某些特殊的Bean的處理中,比如Bean本身就是一個工廠,那麼在其進行單獨的例項化操作邏輯中,可能我們並不想走spring的那一套邏輯,此時就可以實現FactoryBean介面自己控制邏輯。

4.2:spring如何解決迴圈依賴問題

迴圈依賴問題就是A->B->A,spring在建立A的時候,發現需要依賴B,因為去建立B例項,發現B又依賴於A,又去建立A,因為形成一個閉環,無法停止下來就可能會導致cpu計算飆升

如何解決這個問題呢?spring解決這個問題主要靠巧妙的三層快取,所謂的快取主要是指這三個map,singletonObjects主要存放的是單例物件,屬於第一級快取;singletonFactories屬於單例工廠物件,屬於第二級快取;earlySingletonObjects屬於第二級快取,如何理解early這個標識呢?它表示只是經過了例項化尚未初始化的物件。Spring首先從singletonObjects(一級快取)中嘗試獲取,如果獲取不到並且物件在建立中,則嘗試從earlySingletonObjects(二級快取)中獲取,如果還是獲取不到並且允許從singletonFactories通過getObject獲取,則通過singletonFactory.getObject()(三級快取)獲取。如果獲取到了則則移除對應的singletonFactory,將singletonObject放入到earlySingletonObjects,其實就是將三級快取提升到二級快取,這個就是快取升級。spring在進行物件建立的時候,會依次從一級、二級、三級快取中尋找物件,如果找到直接返回。由於是初次建立,只能從第三級快取中找到(例項化階段放入進去的),建立完例項,然後將快取放到第一級快取中。下次迴圈依賴的再直接從一級快取中就可以拿到例項物件了。

  五:測試

我們來寫一個測試類,驗證一下上面的問題:

5.1:首先宣告一個自定義的Bean

@Component
public class CustomBean {
    public CustomBean(){
        System.out.println("呼叫CustomBean空的構造方法");
    }
}

5.2:宣告一個Bean來實現BeanPostProcessor

package com.wyq.spring.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.beans.PropertyDescriptor;

/**
 * @Author: wyq
 * @Desc:
 * @Date: 2019/9/1 15:36
 **/
@Component
@Scope("singleton")
public class TestBean implements BeanPostProcessor, SmartInitializingSingleton, InstantiationAwareBeanPostProcessor, DisposableBean{

    private static final String BEAN_NAME= "customBean";

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (BEAN_NAME.equals(beanName)) {
            System.out.println("==>BeanPostProcessor.postProcessBeforeInitialization");
        }
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (BEAN_NAME.equals(beanName)) {
            System.out.println("==>BeanPostProcessor.postProcessAfterInitialization");
        }
        return null;
    }


    @Override
    public void afterSingletonsInstantiated() {
        System.out.println("==>SmartInitializingSingleton.afterSingletonsInstantiated");

    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (BEAN_NAME.equals(beanName)) {
            System.out.println("==>InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation");
        }
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if (BEAN_NAME.equals(beanName)) {
            System.out.println("==>InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation");
        }
        return false;
    }

    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        System.out.println("==>InstantiationAwareBeanPostProcessor.postProcessPropertyValues");
        return null;
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("==>DisposableBean.destroy");
    }
}

5.3:啟動容器:

六:總結

      本篇部落格主要是介紹了Spring的一些例項化的過程,高屋建瓴的分析了一下spring的bean載入過程,沒有詳細展開某個細節分析。spring的內部原始碼非常複雜,每個介面的實現類都在5個以上,如果深入細節,恐怕不是一篇部落格能講清楚的。這篇部落格的目的就是在闡述spring的基本脈絡中心路線順序,首先我們需要有一個總體的認識,然後再深入到細節就是輕而易舉的了。這也是一種學習的方法論,通過本篇部落格我希望能梳理清楚spring的基本流程,對spring有一個比較清晰的認識。並且學習到優秀開源框架的設計基本思想,還有就是進一步提升自己的閱讀原始碼的能力。

&n