Spring 核心組件原理解析

儘管希臘哲學家赫拉克利特(Heraclitus)並不做爲一名軟件開發人員而聞名,但他彷佛深諳此道。他的一句話常常被引用:「惟一不變的就是變化」,這句話抓住了軟件開發的真諦。spring

咱們如今開發應用的方式和1年前、5年前、10年前都是不一樣的,更別提15年前了,當時RodJohnson的圖書 Expert One-on-One J2EE Design and Development 介紹了Spring框架的初始形態。當時,最多見的應用形式是基於瀏覽器的Web應用,後端由關係型數據庫做爲支撐。儘管這種形式的開發依然有它的價值,Spring也爲這種應用提供了良好的支持,可是咱們如今感興趣的還包括如何開發面向雲的由微服務組成的應用,這些應用會將數據保存到各類類型的數據庫中。數據庫

另一個嶄新的關注點是反應式編程,它致力於經過非阻塞操做提供更好的擴展性並提高性能。隨着軟件開發的發展,Spring框架也在不斷變化,以解決現代應用開發中的問題,其中就包括微服務和反應式編程。Spring還經過引入Spring Boot簡化本身的開發模型。編程

Spring 的核心

任何實際的應用程序都是由不少組件組成的,每一個組件負責整個應用功能的一部分,這些組件須要與其餘的應用元素進行協調以完成本身的任務。當應用程序運行時,須要以某種方式建立並引入這些組件。後端

 

 

Spring Framework 總共有十幾個組件,但真正核心的組件只有三個:Spring Core,Spring Context 和 Spring Bean,它們奠基了 Spring 的基礎並撐起了 Spring 的框架結構。Spring 的其它功能特性例如 Web、AOP、JDBC 等都是在其基礎上發展實現的。瀏覽器

Spring之中最重要的當屬Bean了,Spring實際上就是面向Bean的編程,Bean對於Spring的意義就比如Object對於OOP的意義同樣。那麼,三個核心組件之間是如何協同工做的呢?若是把Bean比做一場演出中的演員,那麼Context就是這場演出的舞臺,Core就是演出的道具,至於演出的節目,就是Spring的一系列特點功能了。框架

咱們知道Bean包裹的是Object,而Object中必然有數據,Context就是給這些數據提供生存環境,發現每一個Bean之間的關係,爲他們創建並維護好這種關係。這樣來講,Context就是一個Bean關係的集合,這個關係集合就是咱們所說的IOC容器。那麼Core又有什麼做用呢?Core就是發現、創建和維護每一個Bean之間的關係所需的一系列工具,就是咱們常常說的Util。ide

Bean 組件

Bean組件在Spring的org.springframework.beans包下,主要完成了Bean的建立、Bean的定義以及Bean的解析三件事。函數

SpringBean的建立是典型的工廠模式,其工廠的繼承層次關係如圖所示:微服務

 

 

Spring 使用工廠模式來管理程序中使用的對象(Bean),Bean 工廠最上層的接口爲 BeanFactory,簡單來看,工廠就是根據須要返回相應的 Bean 實例。工具

public interface BeanFactory {
    //...        
    Object getBean(String name);
}

在工廠模式中,在工廠的實現類中生成 Bean 返回給調用客戶端,這就要求客戶端提供生成本身所需類實例的工廠類,增長客戶負擔。Spring 結合控制反轉和依賴注入爲客戶端提供所需的實例,簡化了客戶端的操做。具體的實現方式大體以下。

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    /** Map of bean definition objects, keyed by bean name */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>;
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){     
        //...
    }
}

beanDefinitionMap 做爲具體的 Bean 容器,Spring 建立的對象實例保存其中。客戶端須要時,使用工廠的 getBean 方法去試圖獲得相應的實例,若是實例已存在,則返回該實例;若是實例不存在,則首先產生相應實例並經過 registerBeanDefinition 方法將其保存在 beanDefinitionMap 中(Lazy Initialization),而後返回該實例給客戶端。

 

 

Spring Bean 工廠的繼承關係beanDefinitionMap 並不直接保存實例自己,而是將實例封裝在 BeanDefinition 對象後進行保存。BeanDefinition 包含了實例的全部信息,其簡化版的定義以下。

public class BeanDefinition {
    private Object bean;

    private Class<?> beanClass;
    private String beanClassName;

    // Bean 屬性字段的初始化值
    private BeanPropertyValues beanPropertyValues;

    //...
}

Spring Bean 工廠生產 Bean 時

  1. 先將實例的類型參數保存到 beanClass 和 beanClassName,將須要初始化的字段名和值保存到 beanPropertyValues 中,這個過程 Spring 經過控制反轉來實現,本文第二小節將予以簡要說明
  2. 生成 bean 實例,並利用反射機制將須要初始化的字段值寫入 bean 實例,將實例保存在 bean 中,完成 BeanDefinition 的構建。 
    假設咱們已經完成了步驟 1) 的操做,以後的過程用代碼表述以下所示。
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
    //生成 bean 實例,並完成初始化
    Object bean = createBean(beanDefinition);
    //將 bean 實例保存在 beanDefinition 中
    beanDefinition.setBean(bean);
    //將 beanDefinition 實例保存在 Spring 容器中
    beanDefinitionMap.put(beanName, beanDefinition);
}

protected Object createBean(BeanDefinition beanDefinition) {
    try{
        Object bean = beanDefinition.getBeanClass().newInstance();
        try {
            setBeanPropertyValues(bean, beanDefinition);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException e) {
            e.printStackTrace();
        }
        return bean;
    }catch(InstantiationException e){
        e.printStackTrace();
    }catch(IllegalAccessException e){
        e.printStackTrace();
    }

    return null;
}

protected void setBeanPropertyValues(Object bean, BeanDefinition beanDefinition) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
    for(PropertyValue pv : beanDefinition.getBeanPropertyValues().getBeanPropertyValues()){
        Field beanFiled = bean.getClass().getDeclaredField(pv.getName());
        beanFiled.setAccessible(true);
        beanFiled.set(bean, pv.getValue());
    }
}

Context 組件

前面說到,Context組件的做用是給Spring提供一個運行時的環境,用以保存各個對象的狀態,咱們來看一下與Context相關的類結構圖。

 

 

從圖中能夠看出,Context類結構的頂級父類是ApplicationContext,它除了能標識一個應用環境的基本信息之外,還繼承了5個接口,這5個接口主要是擴展了Context的功能。ApplicationContext的子類主要包含兩個方向,圖中已做說明。再往下就是構建Context的文件類型,接着就是訪問Context的方式。

通常地,傳統的程序設計中,不管是使用工廠建立實例,或是直接建立實例,實例調用者都要先主動建立實例,然後才能使用。控制反轉(Inverse of Control) 將實例的建立過程交由容器實現,調用者將控制權交出,是所謂控制反轉。
依賴注入(Dependence Injection) 在控制反轉的基礎上更進一步。若是沒有依賴注入,容器建立實例並保存後,調用者須要使用 getBean(String beanName) 才能獲取到實例。使用依賴注入時,容器會將 Bean 實例自動注入到完成相應配置的調用者,供其進一步使用。Context 組件藉助上述的控制反轉和依賴注入,協助實現了 Spring 的 Ioc 容器。下面咱們以一個 Service 類做爲所需的 Bean 實例進行說明。實際應用中,咱們會須要 Spring 管理不少 Bean 實例。

public class SampleService {
    private String service;
        public String getService() {
        return service;
    }
    public void setService(String service) {
        this.service= service;
    }
}

 

在程序運行過程當中,須要一個 SampleService ,咱們不讓調用者 new 一個實例,而是在配置文件中代表該 SampleService 的實例交由 Spring 容器進行管理,並指定其初始化參數。配置文件即資源,其內容以下。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean name="sampleService " class="com.service.SampleService ">
        <property name="service" value="This is a service"></property>
    </bean>
</beans>

 

Spring Core 組件提供 ResourceLoader 接口,便於讀入 xml 文件或其餘資源文件。其核心功能代碼應該提供以下方法。

public class ResourceLoader {
    public Resource getResource(String location){
        URL resource = this.getClass().getClassLoader().getResource(location);
        return new UrlResource(resource);
    }
}

// UrlResource 的功能代碼
public class UrlResource implements Resource {
    private final URL url;

    public UrlResource(URL url){
        this.url = url;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        return urlConnection.getInputStream();
    }

}

即加載資源文件,並以數據流的形式返回。Context 根據資源中的定義,生成相應的 bean 並保存在容器中,bean 的名字是 sampleService ,供程序進一步使用。這樣就完成了控制反轉的工做。接下來就須要把 sampleService 注入到須要使用它的地方,亦即完成依賴注入操做。如今假設 SampleController 中使用 SampleService 的對象,Spring 提供三種依賴注入的方式,構造器注入、setter 注入和註解注入。

public class SampleController {
    /**
     * 3\. 註解注入
    **/
    /* @Autowired */
    private SampleService sampleService;

    /**
     * 1\. 構造器注入
    **/
    public SampleController(SampleService sampleService){
        this.sampleService = sampleService;
    }
    //無參構造函數
    public SampleController(){}

    // 類的核心功能
    public void process(){
        System.out.println(sampleService.getService());
    }
    /**
     * 2\. setter 注入
    **/
    /*public void setService(SampleService service) {
        this.service= service;
    }*/
}

 

三種注入方式在配置文件中對應不一樣的配置方式,在前面 xml 文件的基礎上,咱們能夠分別實現這三種注入方式。須要注意的是,這裏 SampleController 也是使用 Spring 的 Ioc 容器生成管理的。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean name="sampleService " class="com.service.SampleService ">
        <property name="service" value="This is a service"></property>
    </bean>

<!-- 1\. 構造器注入方式爲SampleContorller 的 bean 注入 SampleService -->
    <bean name="sampleContorller" class="com.controller.SampleContorller">
        <!-- index 是構造方法中相應參數的順序 -->
        <constructor-arg index="0" ref="sampleService"></constructor-arg>
    </bean>

<!-- 2\. setter 注入方式爲SampleContorller 的 bean 注入 SampleService -->
<!--
    <bean name="sampleContorller" class="com.controller.SampleContorller">
        <property name="sampleService " ref="sampleService"></property>
    </bean>
-->

<!-- 3\. 註解注入方式爲SampleContorller 的 bean 注入 SampleService -->
<!--
    <bean name="sampleContorller" class="com.controller.SampleContorller">

    <!-- 不須要配置,Spring 自動按照類型注入相應的 bean -->
    </bean>
-->
</beans>

Core組件

Core組件一個重要的組成部分就是定義了資源的訪問方式。Core組價把全部的資源都抽象成一個接口,這樣,對於資源使用者來講,不須要考慮文件的類型。對資源提供者來講,也不須要考慮如何將資源包裝起來交給別人使用(Core組件內全部的資源均可以經過InputStream類來獲取)。另外,Core組件內資源的加載都是由ResourceLoader接口完成的,只要實現這個接口就能夠加載全部的資源。

 

 

那麼,Context和Resource是如何創建關係的呢?經過前面Context的介紹咱們知道,Context組件裏面的類或者接口最終都實現了ResourcePatternResolver接口,ResourcePatternResolver接口的做用就是加載、解析和描述資源。這個接口至關於Resource裏面的一個接頭人,它把Resource裏的資源加載、解析和定義整合到一塊兒,便於其餘組件使用。

前面介紹了三大核心組件的結構與相互關係,那麼,這三大組件是如何讓Spring完成諸如IOC和AOP等各類功能的呢?敬請期待下一篇文章!

最後留一個問題:Spring 爲何可以長盛不衰?

相關文章
相關標籤/搜索