1. 程式人生 > >Spring原始碼解讀之核心容器上節

Spring原始碼解讀之核心容器上節

Spring架構圖

Spring原始碼解讀之核心容器上節

說明

Spring的流行程度就不用我來說了,相信大家如果使用JAVA開發就一定知道它。寫這篇文章的初衷在於:
1.瞭解Spring底層實現原理,提升對Spring的認識與理解。
2.學習優秀框架程式設計實現,學習優秀的設計模式。
3.使用Spring三年多,對於底層細節希望知道更多,便於求職。

對於Spring整個架構是很龐大的,很難一下看完和思考完,所以我會從Core Container進行切入,一步一步往上走,從而解開Spring神祕的底層面紗。同時對Spring的IOC\AOP\事務管理\DI等經典特性做原始碼解讀,過程中會穿插原始碼設計模式的講解以及一些簡單的驗證demo程式碼。對於任何階段想要了解Spring底層原理和設計的JAVA開發或者感興趣的人都可以看這篇文章。

版本、工具說明

Spring版本:5.1.3.RELEASE
JDK:jdk1.8.0_181
開發工具:Intellij IDEA 2018.1.6 (UE)
作業系統:Linux mint 19
包管理工具:Maven 3.3.9

Core Container

核心容器:spring-beans、spring-context、spring-core、Spring-Expression四個元件組成。如下圖:
Spring原始碼解讀之核心容器上節
在介紹這四個元件之前,我們先來寫一個簡單的demo,通過xml配置bean,然後測試獲取一個bean的demo。

Demo

員工類:

package com.ckmike.beans;

/**
 * Person 簡要描述
 * <p> TODO:描述該類職責 </p>
 *
 * @author ckmike
 * @version 1.0
 * @date 18-12-14 下午5:46
 * @copyright ckmike
 **/
public class Employee {

    private int id;
    private String name;
    private int age;
    private String address;
    private String description;

    public Person() {
        this.id = 1;
        this.name = "ckmike";
        this.age = 18;
        this.address = "深圳";
        this.description = "Java developer";
    }

    public void setId(int id) {
        this.id = id;
    }

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

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

    public void setAddress(String address) {
        this.address = address;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getAddress() {
        return address;
    }

    public String getDescription() {
        return description;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                ", description='" + description + '\'' +
                '}';
    }
}

Spring-beans.xml檔案配置bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="employee" name="employee" class="com.ckmike.beans.Employee"></bean>
</beans>

測試程式碼:

import com.ckmike.beans.Employee;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * SpringCoreTest 簡要描述
 * <p> TODO:描述該類職責 </p>
 *
 * @author ckmike
 * @version 1.0
 * @date 18-12-14 下午5:55
 * @copyright ckmike
 **/
public class SpringCoreTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-beans.xml");

        Employee person = (Employee) applicationContext.getBean("employee");

        System.out.println(person.toString());
    }
}

pom.xml檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>Spring-Core-Demo</groupId>
    <artifactId>com.ckmike.springcore.demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- core模組 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!-- Beans模組 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!-- context模組 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
        <!-- expression模組 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
    </dependencies>
</project>

Spring原始碼解讀之核心容器上節
Spring原始碼解讀之核心容器上節

分析:在這個demo裡,用到了spring-beans.xml配置bean,使用ClassPathXmlApplicationContext載入spring-beans.xml檔案,通過getBean()方法獲取Employee例項bean.

ClassPathXmlApplicationContext原始碼

package org.springframework.context.support;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {

    @Nullable
    private Resource[] configResources;

    public ClassPathXmlApplicationContext() {   }

    public ClassPathXmlApplicationContext(ApplicationContext parent) {
        super(parent);
    }

    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

    public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
        this(configLocations, true, null);
    }

    public ClassPathXmlApplicationContext(String[] configLocations, @Nullable ApplicationContext parent)
            throws BeansException {

        this(configLocations, true, parent);
    }

    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
        this(configLocations, refresh, null);
    }

    public ClassPathXmlApplicationContext(
            String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

    public ClassPathXmlApplicationContext(String path, Class<?> clazz) throws BeansException {
        this(new String[] {path}, clazz);
    }

    public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz) throws BeansException {
        this(paths, clazz, null);
    }

    public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent)
            throws BeansException {

        super(parent);
        Assert.notNull(paths, "Path array must not be null");
        Assert.notNull(clazz, "Class argument must not be null");
        this.configResources = new Resource[paths.length];
        for (int i = 0; i < paths.length; i++) {
            this.configResources[i] = new ClassPathResource(paths[i], clazz);
        }
        refresh();
    }

    @Override
    @Nullable
    protected Resource[] getConfigResources() {
        return this.configResources;
    }
}

我們同時檢視下ClassPathXmlApplicationContext的依賴類圖:
Spring原始碼解讀之核心容器上節

上面的圖是不是看著很複雜?看到這裡你是不是已經打算放棄了?其實我們可以關注幾個點:
1.ResouceLoader介面
2.InitializingBean介面
3.BeanFactory介面
4.ApplicationContext介面
5.LifeCycle介面
6.BeanNameAware介面
7.AbstractRefreshableConfigApplicatonContext類
結合這幾個點,我們就可以大致瞭解整個過程中都做了什麼。我們不要想著一次性把所有都看懂,循序漸進是一個很好的方法,畢竟設計者也不是一下就設計好的,所以不要氣餒。

ResourceLoader介面

package org.springframework.core.io;

import org.springframework.lang.Nullable;

public interface ResourceLoader {
    String CLASSPATH_URL_PREFIX = "classpath:";

    Resource getResource(String var1);

    @Nullable
    ClassLoader getClassLoader();
}

從途中可以知道DefaultResourceLoader實現了這個介面,AbstractApplicationContext繼承了DefaultResourceLoader,我們可以繼續看DefaultResourceLoader,
AbstractApplicationContext原始碼,這裡我就不貼出來了,太多。從這裡其實我們就很清楚知道,利用這個介面載入從classpath下載入xml檔案。

InitializingBean介面

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

從類圖我們知道這個介面AbstractRefreshableConfigApplicationContext類實現了,那麼我們繼續看AbstractRefreshableConfigApplicationContext對這個介面實現:
Spring原始碼解讀之核心容器上節

那麼我們繼續看refresh()方:

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

該方法是在AbstractApplicationContext中,這個類有必要仔細檢視一下程式碼,因程式碼過多不貼出來。自行檢視。

BeanFactory介面

package org.springframework.beans.factory;

import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    <T> ObjectProvider<T> getBeanProvider(Class<T> var1);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType var1);

    boolean containsBean(String var1);

    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}

BeanFactory介面被AbstractApplicationContext類實現了這個介面類。這個也是我們非常熟悉的工廠模式。建議要重點去看看AbstractApplicationContext類的實現。

總結

1.spring-beans和spring-core模組是Spring框架的核心模組,包含了控制反轉(Inversion of Control, IoC)和依賴注入(Dependency Injection, DI)。BeanFactory 介面是Spring框架中的核心介面,它是工廠模式的具體實現。BeanFactory 使用控制反轉對應用程式的配置和依賴性規範與實際的應用程式程式碼進行了分離。但 BeanFactory 容器例項化後並不會自動例項化 Bean,只有當 Bean 被使用時 BeanFactory 容器才會對該 Bean 進行例項化與依賴關係的裝配。

2.spring-context模組構架於核心模組之上,他擴充套件了BeanFactory,為它添加了Bean生命週期控制、框架事件體系以及資源載入透明化等功能。此外該模組還提供了許多企業級支援,如郵件訪問、遠端訪問、任務排程等,ApplicationContext是該模組的核心介面,她是 BeanFactory 的超類,與 BeanFactory 不同,ApplicationContext 容器例項化後會自動對所有的單例 Bean 進行例項化與依賴關係的裝配,使之處於待用狀態。

3.spring-expression模組是統一表達式語言(unified EL)的擴充套件模組,可以查詢、管理執行中的物件,同時也方便的可以呼叫物件方法、運算元組、集合等。它的語法類似於傳統EL,但提供了額外的功能,最出色的要數函式呼叫和簡單字串的模板函式。這種語言的特性是基於 Spring 產品的需求而設計,他可以非常方便地同Spring IoC進行互動。

我也是第一閱讀Spring原始碼,有錯誤的地方希望包含且留言告訴我,我會第一時間改過來,防止誤導讀者。這一篇內容因為過多,所以我打算分上下兩篇寫。謝謝閱讀。