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

Spring源碼解讀之核心容器上節

nag bean 表達式 emp anti tab ade injection intro

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四個組件組成。如下圖:
技術分享圖片
在介紹這四個組件之前,我們先來寫一個簡單的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>

技術分享圖片
技術分享圖片

分析:在這個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的依賴類圖:
技術分享圖片

上面的圖是不是看著很復雜?看到這裏你是不是已經打算放棄了?其實我們可以關註幾個點:
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對這個接口實現:
技術分享圖片

那麽我們繼續看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類的實現。

總結

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

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

   spring-expression模塊是統一表達式語言(unified EL)的擴展模塊,可以查詢、管理運行中的對象,同時也方便的可以調用對象方法、操作數組、集合等。它的語法類似於傳統EL,但提供了額外的功能,最出色的要數函數調用和簡單字符串的模板函數。這種語言的特性是基於 Spring 產品的需求而設計,他可以非常方便地同Spring IoC進行交互。

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

Spring源碼解讀之核心容器上節