Spring源碼解析----SpringBoot啟動(dòng)流程

一個(gè)簡單的SB程序如下,點(diǎn)擊main方法左邊的原諒色的三角形就能把程序啟動(dòng)起來,雖然什么功能都沒有,但是啟動(dòng)做了很多處理,加載了很多支持多種功能的組件(類似使用new ClassPathXmlApplicationContext()啟動(dòng)一個(gè)Spring程序,其中加載了很多東西)

@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }

}

分析的入口就是從SpringApplication的run方法開始,而@SpringBootApplication注解在后續(xù)的處理中會(huì)用到,所以暫時(shí)忽略

1.入口

進(jìn)入run方法

    public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

2.初始化SpringApplication

首先初始化了SpringApplication,然后再調(diào)用其run方法,那么先看下SpringApplication的構(gòu)造方法里的邏輯

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        // 即demo里的SpringBootDemoApplication對(duì)應(yīng)的Class對(duì)象集合
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 應(yīng)用的類型,共三種類型
        // reactive、servlet  、非web應(yīng)用
        this.webApplicationType = deduceWebApplicationType();
        // 獲取spring.factories中配置的ApplicationContextInitializer的實(shí)現(xiàn)類并實(shí)例化,最后放到集合當(dāng)中
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        // 原理同上
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 找到執(zhí)行main方法的那個(gè)類的Class對(duì)象,即demo中的SpringBootDemoApplication對(duì)應(yīng)的Class對(duì)象
        this.mainApplicationClass = deduceMainApplicationClass();
    }

2.1獲取應(yīng)用類型

SpringApplication構(gòu)造方法里,調(diào)用了deduceWebApplicationType來判斷是什么類型的應(yīng)用,那么SpringBoot是如何判斷的呢?往里進(jìn)去看下具體實(shí)現(xiàn)

    private WebApplicationType deduceWebApplicationType() {
        //REACTIVE_WEB_ENVIRONMENT_CLASS = org.springframework.web.reactive.DispatcherHandler
        //MVC_WEB_ENVIRONMENT_CLASS = org.springframework.web.servlet.DispatcherServlet
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        //WEB_ENVIRONMENT_CLASSES=javax.servlet.Servlet
        // 或者org.springframework.web.context.ConfigurableWebApplicationContext
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

ClassUtils.isPresent方法中嘗試加載傳入的類,如果加載成功,則返回true,如果失敗則返回false,SpringBoot使用這種方式在判斷當(dāng)前是什么類型的應(yīng)用。

從假設(shè)我們應(yīng)用中什么依賴都沒有加入,那么WEB_ENVIRONMENT_CLASSES、REACTIVE_WEB_ENVIRONMENT_CLASS或者MVC_WEB_ENVIRONMENT_CLASS都是加載失敗的,最終返回WebApplicationType.NONE。

從另一個(gè)方面來說,如果我們想構(gòu)建一個(gè)SpringMVC的web應(yīng)用,那么只需要加入如下依賴:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

此時(shí),SpringBoot會(huì)幫我們間接依賴SpringMVC所需要的包,那么就間接引入了org.springframework.web.context.ConfigurableWebApplicationContext這個(gè)包,由于SpringMVC底層是基于Servlet,那么會(huì)引入javax.servlet.Servlet相關(guān)的包。加了上面這一個(gè)依賴之后,deduceWebApplicationType方法就會(huì)返回WebApplicationType.SERVLET。

后續(xù)SpringBoot會(huì)根據(jù)deduceWebApplicationType的返回類型,來做不同的初始化,這也算是SpringBoot自動(dòng)化的其中一個(gè)體現(xiàn)。

2.2 Spring的擴(kuò)展機(jī)制

在構(gòu)造方法中,會(huì)初始化initializers和listeners兩個(gè)幾個(gè),里面的對(duì)應(yīng)的類型分別是ApplicationContextInitializerApplicationListener類型的實(shí)例。

  • ApplicationContextInitializer:用于上下文刷新前(refreshContext)的回調(diào)處理
  • ApplicationListener:用于事件的通知

這兩個(gè)集合都是通過getSpringFactoriesInstances方法去加載的,而核心的實(shí)現(xiàn)就是Spring的擴(kuò)展機(jī)制。

SpringBoot會(huì)基于這個(gè)機(jī)制,會(huì)提供一些系統(tǒng)內(nèi)置的類(分別實(shí)現(xiàn)ApplicationContextInitializer和ApplicationListener),讓系統(tǒng)自動(dòng)的調(diào)用對(duì)應(yīng)的實(shí)現(xiàn),而這些內(nèi)置類的加載則是通過getSpringFactoriesInstances方法實(shí)現(xiàn)的:

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        //獲取類加載器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 獲取對(duì)應(yīng)的類名
        Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //實(shí)例化
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

SpringFactoriesLoader就是擴(kuò)展機(jī)制的核心實(shí)現(xiàn),其大概邏輯就是,查找META-INF文件下的spring.factories文件,通過對(duì)應(yīng)類型獲取配置好的類名,以ApplicationContextInitializer為例,引入的jar包中,其下面的spring.factories文件如下:

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer 

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

getSpringFactoriesInstances(ApplicationContextInitializer.class)得到的是的是上面6個(gè)類的實(shí)例對(duì)象集合,ApplicationListener就不再分析

基于此,我們也可以在工程的對(duì)應(yīng)路徑下加入spring.factories文件,并用上面的規(guī)則,加入自己的實(shí)現(xiàn)來擴(kuò)展對(duì)應(yīng)的功能

3.SpringApplication.run方法

當(dāng)SpringApplication構(gòu)造完成,就進(jìn)入到run方法,進(jìn)行真正的初始化步驟

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //設(shè)置awt相關(guān)屬性
        configureHeadlessProperty();
        // 通過擴(kuò)展機(jī)制獲取SpringApplicationRunListener的實(shí)現(xiàn)類,并封裝成SpringApplicationRunListeners對(duì)象
        // 目前只有一個(gè)實(shí)現(xiàn)EventPublishingRunListener
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 發(fā)布ApplicationStartingEvent事件
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //環(huán)境準(zhǔn)備
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            //設(shè)置需要忽略的Bean信息
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            //根據(jù)應(yīng)用類型創(chuàng)建對(duì)應(yīng)的ApplicationContext
            context = createApplicationContext();
            //獲取錯(cuò)誤報(bào)告處理器
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            //刷新上下文前的準(zhǔn)備工作
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            //刷新上下文,即Spring的核心流程,IOC,AOP,Bean初始化等流程
            refreshContext(context);
            //刷新完成后置處理
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            //發(fā)布ApplicationStartedEvent事件
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
                //....
        }
                //發(fā)布ApplicationReadyEvent事件
        listeners.running(context);
        return context;
    }

下面對(duì)核心步驟進(jìn)行分析

3.1 環(huán)境準(zhǔn)備

環(huán)境主要指的是Environment,Environment主要包括兩部分功能:

  • profile
  • properties

其中profile是Environment自身的功能,而properties相關(guān)功能則是由Environment繼承的接口PropertyResolver提供

prepareEnvironment方法會(huì)構(gòu)建一個(gè)ConfigurableEnvironment實(shí)例,并對(duì)其進(jìn)行初始化

    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // 根據(jù)應(yīng)用類型返回不同的實(shí)例
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // properties和profile相關(guān)處理
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        //發(fā)布ApplicationEnvironmentPreparedEvent事件
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (this.webApplicationType == WebApplicationType.NONE) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

先看下getOrCreateEnvironment方法

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        if (this.webApplicationType == WebApplicationType.SERVLET) {
            return new StandardServletEnvironment();
        }
        return new StandardEnvironment();
    }

由于是Servlet的web應(yīng)用,則返回StandardServletEnvironment實(shí)例,其他情況則返回StandardEnvironment(StandardEnvironment是StandardServletEnvironment的父類)。這里為什么Servlet應(yīng)用需要特殊處理呢?

前面說過Environment包括properties和profile,我們知道Servlet的properties來源和其他類型的應(yīng)用相比,還有ServletContext里的參數(shù),那么Servlet應(yīng)用需要另外的解析工作(基礎(chǔ)解析工作由StandardEnvironment完成),而這個(gè)工作需要由StandardServletEnvironment特殊處理。

在其父類AbstractEnvironment的構(gòu)造方法中,會(huì)調(diào)用到customizePropertySources方法,這個(gè)方法由子類重寫來自定義解析策略(實(shí)際是加入處理的PropertySource),StandardServletEnvironmentcustomizePropertySources方法如下:

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
        propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
        }
        super.customizePropertySources(propertySources);
    }

這里看到該方法中加入兩個(gè)PropertySource用于Servlet特有的屬性解析,然后再調(diào)用父類StandardEnvironment的方法保證基礎(chǔ)的屬性解析也沒有問題,看下StandardEnvironment的方法

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

這里加入了兩個(gè)PropertySource用于解析其他屬性,即System.getProperties()和System.getEnv()兩個(gè)方法所附帶的屬性。

  • 至于PropertySource所涉及的屬性解析原理,這里不再展開,后續(xù)會(huì)另起文章詳細(xì)分析屬性解析原理。

環(huán)境Environment的準(zhǔn)備中,主要是為屬性解析和profile做了準(zhǔn)備工作,例如添加了PropertySource待后續(xù)參數(shù)獲取使用

3.2 創(chuàng)建對(duì)應(yīng)的ApplicationContext

創(chuàng)建上下文主要交由createApplicationContext處理

    public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
            + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
    public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
    public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

邏輯比較簡單,根據(jù)應(yīng)用類型創(chuàng)建不同的上下文對(duì)象

3.3 刷新上下文前的準(zhǔn)備工作

該工作交由prepareContext處理:

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        // 后置處理,注冊(cè)BeanNameGenerator和設(shè)置ResourceLoader
        postProcessApplicationContext(context);
        // 遍歷initializers集合內(nèi)的ApplicationContextInitializer實(shí)例進(jìn)行調(diào)用
        applyInitializers(context);
        // 發(fā)布事件,目前看到的實(shí)現(xiàn)為空
        listeners.contextPrepared(context);
        // 將之前封裝的參數(shù)對(duì)象注冊(cè)到容器中
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
        // 即上文提到的primarySources
        Set<Object> sources = getAllSources();
        // 將我們的啟動(dòng)類注冊(cè)到容器中      
        load(context, sources.toArray(new Object[0]));
        //發(fā)布ApplicationPreparedEvent事件
        listeners.contextLoaded(context);
    }

3.3.1 postProcessApplicationContext

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
        if (this.beanNameGenerator != null) {
            context.getBeanFactory().registerSingleton(
                    AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                    this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            if (context instanceof GenericApplicationContext) {
                ((GenericApplicationContext) context)
                        .setResourceLoader(this.resourceLoader);
            }
            if (context instanceof DefaultResourceLoader) {
                ((DefaultResourceLoader) context)
                        .setClassLoader(this.resourceLoader.getClassLoader());
            }
        }
    }

代碼邏輯很簡單,但是該方法中beanNameGeneratorresourceLoader都為空,所以這個(gè)地方是Springboot留的一個(gè)擴(kuò)展點(diǎn),可以將自定義的設(shè)置進(jìn)去

3.3.2 applyInitializers

    protected void applyInitializers(ConfigurableApplicationContext context) {
        for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                    initializer.getClass(), ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            initializer.initialize(context);
        }
    }

在前面的分析中,Spring會(huì)從spring.factories中獲取ApplicationContextInitializer的實(shí)現(xiàn)并加入到集合當(dāng)中,而這里就是真正調(diào)用的地方,具體的每個(gè)實(shí)現(xiàn)后續(xù)在相關(guān)功能中進(jìn)行分析

3.3.3 load

    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        BeanDefinitionLoader loader = createBeanDefinitionLoader(
                getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        loader.load();
    }

將啟動(dòng)類封裝成一個(gè)BeanDefinitionLoader,然后設(shè)置beanNameGenerator、resourceLoader、environment,最后調(diào)用load方法,將啟動(dòng)類注冊(cè)到容器當(dāng)中。

主要是創(chuàng)建了一個(gè)AnnotatedGenericBeanDefinition,然后使用BeanDefinitionRegistry,以beanName為key,將其注冊(cè)到容器中。

這里涉及了Spring的知識(shí),就不再詳細(xì)展開。

3.4 刷新上下文

    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }

refresh為Spring的核心流程,即org.springframework.context.support.AbstractApplicationContext#refresh,進(jìn)行bean的創(chuàng)建等等一些核心流程。

另外會(huì)注冊(cè)一個(gè)核心關(guān)閉鉤子。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容