Springboot是咋個(gè)啟動(dòng)的

一段眾所周知的代碼

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

在這段眾所周知的代碼里,最能吸引我的便是Annotation定義(@SpringBootApplication)和類定義(SpringApplication.run)。然而這兩貨確實(shí)是springboot啟動(dòng)的入口和關(guān)鍵。

SpringBootApplication背后的那些事

@Target(ElementType.TYPE)            // 注解的適用范圍,其中TYPE用于描述類、接口(包括包注解類型)或enum聲明
@Retention(RetentionPolicy.RUNTIME)  // 注解的生命周期,保留到class文件中(三個(gè)生命周期)
@Documented                          // 表明這個(gè)注解應(yīng)該被javadoc記錄
@Inherited                           // 子類可以繼承該注解
@SpringBootConfiguration             // 繼承了Configuration,表示當(dāng)前是注解類
@EnableAutoConfiguration             // 開(kāi)啟springboot的注解功能,springboot的四大神器之一,其借助@import的幫助
@ComponentScan(excludeFilters = {    // 掃描路徑設(shè)置(具體使用待確認(rèn))
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
        ...
}

我們可以看到,在SpringbootApplication上有SpringbootConfiguration,EnableAutoConfiguration,ComponentScan三個(gè)注解,所以那段眾所周知的代碼實(shí)際上等價(jià)于

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Configuration說(shuō)明

這里的@Configuration對(duì)我們來(lái)說(shuō)不陌生,它就是JavaConfig形式的Spring Ioc容器的配置類使用的那個(gè)@Configuration,SpringBoot社區(qū)推薦使用基于JavaConfig的配置形式,所以,這里的啟動(dòng)類標(biāo)注了@Configuration之后,本身其實(shí)也是一個(gè)IoC容器的配置類。
舉幾個(gè)簡(jiǎn)單例子回顧下,XML跟config配置方式的區(qū)別:

<?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-3.0.xsd"
       default-lazy-init="true">
    <!--bean定義-->
      <bean id="mockService" class="..MockServiceImpl">
          <propery name ="dependencyService" ref="dependencyService" />
      </bean>
      <bean id="dependencyService" class="DependencyServiceImpl"></bean>
</beans>

而基于JavaConfig的配置方式是這樣:

@Configuration
public class MockConfiguration{
    //或者放在方法參數(shù),都是可以的,如果依賴的比較多,參數(shù)會(huì)很長(zhǎng),如果依賴的比較少,放在參數(shù)里比較簡(jiǎn)單
     @Bean
    public MockService mockService(){
        return new MockServiceImpl(dependencyService());
    }
    
    @Bean
    public DependencyService dependencyService(){
        return new DependencyServiceImpl();
    }
}

ComponentScan說(shuō)明

@ComponentScan這個(gè)注解在Spring中很重要,它對(duì)應(yīng)XML配置中的元素,@ComponentScan的功能其實(shí)就是自動(dòng)掃描并加載符合條件的組件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。
我們可以通過(guò)basePackages等屬性來(lái)細(xì)粒度的定制@ComponentScan自動(dòng)掃描的范圍,如果不指定,則默認(rèn)Spring框架實(shí)現(xiàn)會(huì)從聲明@ComponentScan所在類的package進(jìn)行掃描。
注:所以SpringBoot的啟動(dòng)類最好是放在root package下,因?yàn)槟J(rèn)不指定basePackages。

EnableAutoConfiguration說(shuō)明

個(gè)人感覺(jué)@EnableAutoConfiguration這個(gè)Annotation最為重要,所以放在最后來(lái)解讀,大家是否還記得Spring框架提供的各種名字為@Enable開(kāi)頭的Annotation定義?比如@EnableScheduling、@EnableCaching、@EnableMBeanExport等,@EnableAutoConfiguration的理念和做事方式其實(shí)一脈相承,簡(jiǎn)單概括一下就是,借助@Import的支持,收集和注冊(cè)特定場(chǎng)景相關(guān)的bean定義。

  • @EnableScheduling是通過(guò)@Import將Spring調(diào)度框架相關(guān)的bean定義都加載到IoC容器。
  • @EnableMBeanExport是通過(guò)@Import將JMX相關(guān)的bean定義加載到IoC容器。

而@EnableAutoConfiguration也是借助@Import的幫助,將所有符合自動(dòng)配置條件的bean定義加載到IoC容器,僅此而已!
@EnableAutoConfiguration作為一個(gè)復(fù)合Annotation,其自身定義關(guān)鍵信息如下:

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}

兩個(gè)比較重要的注解:

  • @AutoConfigurationPackage:自動(dòng)配置包
  • @Import: 導(dǎo)入自動(dòng)配置的組件

AutoConfigurationPackage說(shuō)明

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
     @Override
     public void registerBeanDefinitions(AnnotationMetadata metadata,
             BeanDefinitionRegistry registry) {
     register(registry, new PackageImport(metadata).getPackageName());
}

它其實(shí)是注冊(cè)了一個(gè)Bean的定義。
new PackageImport(metadata).getPackageName(),它其實(shí)返回了當(dāng)前主程序類的 同級(jí)以及子級(jí)的包組件。

Import(AutoConfigurationImportSelector.class)注解:

import

可以從圖中看出 AutoConfigurationImportSelector 繼承了 DeferredImportSelector 繼承了 ImportSelector
ImportSelector有一個(gè)方法為:selectImports。

@Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return StringUtils.toStringArray(configurations);
    }

可以看到它其實(shí)是去加載 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";外部文件。這個(gè)外部文件,有很多自動(dòng)配置的類。如下:


spring.factories

其中,最關(guān)鍵的要屬@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以幫助SpringBoot應(yīng)用將所有符合條件的@Configuration配置都加載到當(dāng)前SpringBoot創(chuàng)建并使用的IoC容器。就像一只“八爪魚(yú)”一樣。

自動(dòng)配置幕后英雄:SpringFactoriesLoader詳解

借助于Spring框架原有的一個(gè)工具類:SpringFactoriesLoader的支持,@EnableAutoConfiguration可以智能的自動(dòng)配置功效才得以大功告成!
SpringFactoriesLoader屬于Spring框架私有的一種擴(kuò)展方案,其主要功能就是從指定的配置文件META-INF/spring.factories加載配置。

public abstract class SpringFactoriesLoader {
    //...
    public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
        ...
    }
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        ....
    }
}

配合@EnableAutoConfiguration使用的話,它更多是提供一種配置查找的功能支持,即根據(jù)@EnableAutoConfiguration的完整類名org.springframework.boot.autoconfigure.EnableAutoConfiguration作為查找的Key,獲取對(duì)應(yīng)的一組@Configuration類。
所以,@EnableAutoConfiguration自動(dòng)配置的魔法騎士就變成了:從classpath中搜尋所有的META-INF/spring.factories配置文件,并將其中org.springframework.boot.autoconfigure.EnableutoConfiguration對(duì)應(yīng)的配置項(xiàng)通過(guò)反射(Java Refletion)實(shí)例化為對(duì)應(yīng)的標(biāo)注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總為一個(gè)并加載到IoC容器。

Springboot啟動(dòng)圖

Springboot啟動(dòng)圖

深入探索SpringApplication執(zhí)行流程

SpringApplication的run方法的實(shí)現(xiàn)是我們本次旅程的主要線路,該方法的主要流程大體可以歸納如下:
1) 如果我們使用的是SpringApplication的靜態(tài)run方法,那么,這個(gè)方法里面首先要?jiǎng)?chuàng)建一個(gè)SpringApplication對(duì)象實(shí)例,然后調(diào)用這個(gè)創(chuàng)建好的SpringApplication的實(shí)例方法。在SpringApplication實(shí)例初始化的時(shí)候,它會(huì)提前做幾件事情:

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
        return new SpringApplication(sources).run(args);
    }
  • 根據(jù)classpath里面是否存在某個(gè)特征類(org.springframework.web.context.ConfigurableWebApplicationContext)來(lái)決定是否應(yīng)該創(chuàng)建一個(gè)為Web應(yīng)用使用的ApplicationContext類型。
  • 使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationContextInitializer。
  • 使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationListener。
  • 推斷并設(shè)置main方法的定義類。
@SuppressWarnings({ "unchecked", "rawtypes" })
    private void initialize(Object[] sources) {
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        this.webEnvironment = deduceWebEnvironment();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

2) SpringApplication實(shí)例初始化完成并且完成設(shè)置后,就開(kāi)始執(zhí)行run方法的邏輯了,方法執(zhí)行伊始,首先遍歷執(zhí)行所有通過(guò)SpringFactoriesLoader可以查找到并加載的SpringApplicationRunListener。調(diào)用它們的started()方法,告訴這些SpringApplicationRunListener,“嘿,SpringBoot應(yīng)用要開(kāi)始執(zhí)行咯!”。

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
       // 核心點(diǎn):會(huì)打印springboot的啟動(dòng)標(biāo)志,直到server.port端口啟動(dòng)
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }

3) 創(chuàng)建并配置當(dāng)前Spring Boot應(yīng)用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。

private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        if (!this.webEnvironment) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        return environment;
    }

4) 遍歷調(diào)用所有SpringApplicationRunListener的environmentPrepared()的方法,告訴他們:“當(dāng)前SpringBoot應(yīng)用使用的Environment準(zhǔn)備好了咯!”。

public void environmentPrepared(ConfigurableEnvironment environment) {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.environmentPrepared(environment);
        }
    }

5) 如果SpringApplication的showBanner屬性被設(shè)置為true,則打印banner。

private Banner printBanner(ConfigurableEnvironment environment) {
        if (this.bannerMode == Banner.Mode.OFF) {
            return null;
        }
        ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
                : new DefaultResourceLoader(getClassLoader());
        SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
                resourceLoader, this.banner);
        if (this.bannerMode == Mode.LOG) {
            return bannerPrinter.print(environment, this.mainApplicationClass, logger);
        }
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }

6) 根據(jù)用戶是否明確設(shè)置了applicationContextClass類型以及初始化階段的推斷結(jié)果,決定該為當(dāng)前SpringBoot應(yīng)用創(chuàng)建什么類型的ApplicationContext并創(chuàng)建完成,然后根據(jù)條件決定是否添加ShutdownHook,決定是否使用自定義的BeanNameGenerator,決定是否使用自定義的ResourceLoader,當(dāng)然,最重要的,將之前準(zhǔn)備好的Environment設(shè)置給創(chuàng)建好的ApplicationContext使用。

7) ApplicationContext創(chuàng)建好之后,SpringApplication會(huì)再次借助Spring-FactoriesLoader,查找并加載classpath中所有可用的ApplicationContext-Initializer,然后遍歷調(diào)用這些ApplicationContextInitializer的initialize(applicationContext)方法來(lái)對(duì)已經(jīng)創(chuàng)建好的ApplicationContext進(jìn)行進(jìn)一步的處理。

@SuppressWarnings({ "rawtypes", "unchecked" })
    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);
        }
    }

8) 遍歷調(diào)用所有SpringApplicationRunListener的contextPrepared()方法。

private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources
        Set<Object> sources = getSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[sources.size()]));
        listeners.contextLoaded(context);
    }

9) 最核心的一步,將之前通過(guò)@EnableAutoConfiguration獲取的所有配置以及其他形式的IoC容器配置加載到已經(jīng)準(zhǔn)備完畢的ApplicationContext。

private void prepareAnalyzer(ConfigurableApplicationContext context,
            FailureAnalyzer analyzer) {
        if (analyzer instanceof BeanFactoryAware) {
            ((BeanFactoryAware) analyzer).setBeanFactory(context.getBeanFactory());
        }
    }

10) 遍歷調(diào)用所有SpringApplicationRunListener的contextLoaded()方法。

public void contextLoaded(ConfigurableApplicationContext context) {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.contextLoaded(context);
        }
    }

11) 調(diào)用ApplicationContext的refresh()方法,完成IoC容器可用的最后一道工序。

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

12) 查找當(dāng)前ApplicationContext中是否注冊(cè)有CommandLineRunner,如果有,則遍歷執(zhí)行它們。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<Object>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<Object>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

13) 正常情況下,遍歷執(zhí)行SpringApplicationRunListener的finished()方法、(如果整個(gè)過(guò)程出現(xiàn)異常,則依然調(diào)用所有SpringApplicationRunListener的finished()方法,只不過(guò)這種情況下會(huì)將異常信息一并傳入處理)
去除事件通知點(diǎn)后,整個(gè)流程如下:

public void finished(ConfigurableApplicationContext context, Throwable exception) {
        for (SpringApplicationRunListener listener : this.listeners) {
            callFinishedListener(listener, context, exception);
        }
    }
Springboot啟動(dòng)

到此,SpringBoot的核心組件完成了基本的解析,綜合來(lái)看,大部分都是Spring框架背后的一些概念和實(shí)踐方式,SpringBoot只是在這些概念和實(shí)踐上對(duì)特定的場(chǎng)景事先進(jìn)行了固化和升華,而也恰恰是這些固化讓我們開(kāi)發(fā)基于Sping框架的應(yīng)用更加方便高效。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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