Spring Boot 啟動(dòng)流程

Spring Boot 啟動(dòng)流程

[TOC]

Spring Boot 的程序啟動(dòng)于 SpringApplicationrun 方法,一步步追蹤。

創(chuàng)建 SpringApplication 實(shí)例

最常見的方法是 SpringApplication.run(Xxx.class, args),在這個(gè)方法中,先創(chuàng)建了一個(gè) SpringApplication 實(shí)例,再調(diào)用 run 方法啟動(dòng) Spring。

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);
}

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 根據(jù) classpath 中存在的類推斷出啟動(dòng)的程序是 web 程序還是 webflux 或者普通 java 程序
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 獲取 ApplicationContextInitializer 的實(shí)現(xiàn)類,在 spring.factories 文件中定義的
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 同上,獲取 ApplicationListener 實(shí)現(xiàn)類
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 推斷 main class,可見創(chuàng)建 SpringApplication 實(shí)例的類不一定是執(zhí)行 main 方法的類
    this.mainApplicationClass = deduceMainApplicationClass();
}

從構(gòu)造方法中可以看到 spring boot 只是獲取了一些配置信息,其中有些有意思的東西。

推斷應(yīng)用類型

之前做項(xiàng)目想啟動(dòng)一個(gè) rpc 服務(wù)提供者,并不需要啟動(dòng)一個(gè)內(nèi)嵌的 Tomcat 容器,先去搜了一下發(fā)現(xiàn)是一個(gè)很奇葩的做法,實(shí)現(xiàn) CommandLineRunner 接口并重寫 run() 方法,Google 搜到的前幾條都是這么說(shuō)的,想了下 Spring Boot 怎么會(huì)使用這么蠢的方法?

正確的方法是 classpath 中不存在相關(guān)的類,或者在調(diào)用 run 方法之前使用 SpringApplication.setWebApplicationType(WebApplicationType.NONE) 明確指定是一個(gè)普通的 Java 進(jìn)程。

來(lái)看下 Spring Boot 是怎么實(shí)現(xiàn)自動(dòng)推斷應(yīng)用類型的:

static WebApplicationType deduceFromClasspath() {
    // 存在 org.springframework.web.reactive.DispatcherHandler 類
    // 不存在 org.springframework.web.servlet.DispatcherServlet 和 org.glassfish.jersey.servlet.ServletContainer
    // 就是 Reactive Web 程序
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    // javax.servlet.Servlet 和 org.springframework.web.context.ConfigurableWebApplicationContext 都不存在就是普通 Java 程序
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

也就是從 classpath 中獲取是否存在相應(yīng)類型的類,Reactive 需要的類是在 spring-webflux 包中的,而 ConfigurableWebApplicationContext 是在 spring-web 包中,那么只要項(xiàng)目中不引入這兩個(gè)包就會(huì)啟動(dòng)一個(gè)普通的 Java 進(jìn)程了。

可見所謂的“技術(shù)博客”、“技術(shù)論壇”多么不靠譜了,"Show me your code",一切都在代碼里。(對(duì) Spring 來(lái)說(shuō)它的文檔足夠豐富,大多數(shù)情況不看代碼也行)

spring.factories 文件

推斷過(guò)應(yīng)用類型以后,加載 ApplicationContextInitializerApplicationListener 的實(shí)現(xiàn)類,是從 SpringApplication 的 classpath 下 spring.factories 中加載的:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // 在 LinkedHashSet 中存放類名,保證實(shí)例唯一和有序
    // SpringFactoriesLoader.loadFactoryNames 里邊只是讀取了 spring.factories 中的 k-v 對(duì)并返回 type 為 key 的類名
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // 根據(jù) order 排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

// 只是實(shí)例化類,在上面的步驟中獲取到的都是無(wú)參構(gòu)造方法
@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
        ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

Spring Framework 中提供了 SpringFactoriesLoader 工具類,用于從 spring.factories 文件中讀取出某些特性的實(shí)現(xiàn)類,而 Spring Boot 也是利用這一點(diǎn),從 spring.facotry 中讀取出 EnableAutoConfiguration 的相關(guān)類,從而實(shí)現(xiàn)自動(dòng)化配置。

這個(gè)機(jī)制本質(zhì)上和 Java 的 SPI 沒(méi)有區(qū)別。

run 方法啟動(dòng) Spring Boot 程序

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch(); // 計(jì)時(shí)器,用于統(tǒng)計(jì)啟動(dòng)時(shí)間
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        // 獲取 SpringApplication 的 listener 并通知 SpringApplication 開始啟動(dòng)了
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 配置 environment
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            // Spring Boot Banner 的打印,可以設(shè)置關(guān)閉或自定義 Banner
            Banner printedBanner = printBanner(environment);
            // 創(chuàng)建 ApplicationContext,會(huì)根據(jù)應(yīng)用類型創(chuàng)建,如果是非 Web 應(yīng)用則創(chuàng)建一個(gè) AnnotationConfigApplicationContext 實(shí)例
            context = createApplicationContext();
            // 獲取異常報(bào)告器,用于啟動(dòng)失敗時(shí)打印錯(cuò)誤信息
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            // 準(zhǔn)備 applicaton context,包括
            //   調(diào)用構(gòu)造方法中獲取的 ApplicationContextInitializer 的 initialize 方法
            //   通知 listener contextPrepared 事件
            //   將啟動(dòng)類注冊(cè)為 Spring Bean
            //   通知 listener contextLoaded 事件
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 經(jīng)典的 ApplicationContext.refresh 的調(diào)用
            refreshContext(context);
            // 暫時(shí)是個(gè)空方法
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            // 打印啟動(dòng)信息,主要是應(yīng)用信息以及啟動(dòng)耗時(shí)
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 通知 listener SpringApplication 已經(jīng)啟動(dòng)成功了
            listeners.started(context);
            // 所有步驟執(zhí)行完畢,最后執(zhí)行 ApplicationRunner 和 CommandLineRunner
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            // 如果拋出異常,就打印錯(cuò)誤信息并拋到外層
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            // 通知 listener SpringApplication 正在運(yùn)行
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

至此,SpringApplication 的啟動(dòng)流程就走完了,Spring Boot 的自動(dòng)化配置特性并不是自己?jiǎn)为?dú)開發(fā)的,而是基于 Spring 的 Import 機(jī)制,但是 Import 進(jìn)來(lái)的 Selector 是在 Spring Boot 項(xiàng)目中實(shí)現(xià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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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