Spring Boot 啟動過程分析

1. Spring Boot 入口——main方法

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

從上面代碼可以看出筒狠,Annotation定義(@SpringBootApplication)和類定義(SpringApplication.run)最為耀眼名扛,所以分析 Spring Boot 啟動過程舅踪,我們就從這兩位開始。

2. 核心注解

2.1 @SpringBootApplication

@SpringBootApplication 是最常用也幾乎是必用的注解选侨,源碼如下:

/**
 * Indicates a {@link Configuration configuration} class that declares one or more
 * {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
 * auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
 * annotation that is equivalent to declaring {@code @Configuration},
 * {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
 
 * 標示一個聲明有一個或多個的@Bean方法的Configuration類并且觸發(fā)自動配置(EnableAutoConfiguration)  
 * 和組建掃描(ComponentScan)掖鱼。
 * 這是一個相當于@Configuration、@EnableAutoConfiguration援制、@ComponentScan 三合一的組合注解戏挡。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

從源碼聲明可以看出,@SpringBootApplication相當于 @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan 隘谣,因此我們直接拆開來分析增拥。

2.2 @SpringBootConfiguration

@SpringBootConfiguration 是繼承自Spring的 @Configuration 注解,@SpringBootConfiguration 作用相當于 @Configuration寻歧。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

spring 3.0中增加了@Configuration,@Bean秩仆÷敕海可基于JavaConfig形式對 Spring 容器中的bean進行更直觀的配置。SpringBoot推薦使用基于JavaConfig的配置形式澄耍。

基于xml配置:

<?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 id="mockService" class="..MockServiceImpl">
    ...
    </bean>
</beans>

基于JavaConfig配置:

@Configuration
public class MockConfiguration{
    @Bean
    public MockService mockService(){
        return new MockServiceImpl();
    }
}

總結(jié)噪珊,@Configuration相當于一個spring的xml文件,配合@Bean注解齐莲,可以在里面配置需要Spring容器管理的bean痢站。

2.3 @ComponentScan

@ComponentScan源碼:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    /**
     * 對應(yīng)的包掃描路徑 可以是單個路徑,也可以是掃描的路徑數(shù)組
     * @return
     */
    @AliasFor("basePackages")
    String[] value() default {};
    /**
     * 和value一樣是對應(yīng)的包掃描路徑 可以是單個路徑选酗,也可以是掃描的路徑數(shù)組
     * @return
     */
    @AliasFor("value")
    String[] basePackages() default {};
    /**
     * 指定具體的掃描類
     * @return
     */
    Class<?>[] basePackageClasses() default {};
    /**
     * 對應(yīng)的bean名稱的生成器 默認的是BeanNameGenerator
     * @return
     */
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
    /**
     * 處理檢測到的bean的scope范圍
     */
    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
    /**
     * 是否為檢測到的組件生成代理
     */
    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
    /**
     * 控制符合組件檢測條件的類文件   默認是包掃描下的
     * @return
     */
    String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
    /**
     * 是否對帶有@Component @Repository @Service @Controller注解的類開啟檢測,默認是開啟的
     * @return
     */
    boolean useDefaultFilters() default true;
    /**
     * 指定某些定義Filter滿足條件的組件
     * @return
     */
    Filter[] includeFilters() default {};
    /**
     * 排除某些過來器掃描到的類
     * @return
     */
    Filter[] excludeFilters() default {};
    /**
     * 掃描到的類是都開啟懶加載 阵难,默認是不開啟的
     * @return
     */
    boolean lazyInit() default false;
}

基于xml配置:

<context:component-scan base-package="com.youzan" use-default-filters="false">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

基于JavaConfig配置:

@Configuration
@ComponentScan(value = "com.youzan", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class ScanConfig {
    
}

總結(jié):@ComponentScan通常與@Configuration一起配合使用,相當于xml里面的<context:component-scan>芒填,用來告訴Spring需要掃描哪些包或類呜叫。如果不設(shè)值的話默認掃描@ComponentScan注解所在類的同級類和同級目錄下的所有類空繁,所以對于一個Spring Boot項目,一般會把入口類放在頂層目錄中朱庆,這樣就能夠保證源碼目錄下的所有類都能夠被掃描到盛泡。

2.4 @EnableAutoConfiguration ??

@EnableAutoConfiguration 源碼如下:

/**
 * Enable auto-configuration of the Spring Application Context, attempting to guess and
 * configure beans that you are likely to need. Auto-configuration classes are usually
 * applied based on your classpath and what beans you have defined. For example, If you
 * have {@code tomcat-embedded.jar} on your classpath you are likely to want a
 * {@link TomcatEmbeddedServletContainerFactory} (unless you have defined your own
 * {@link EmbeddedServletContainerFactory} bean).
 * 
 * 啟用Spring應(yīng)用程序上下文的自動配置,試圖猜測和配置您可能需要的bean娱颊。 自動配置類通嘲了校基于您的類路徑和您
 * 定義的bean應(yīng)用。 例如箱硕,如果您在類路徑中有tomcat-embedded.jar掰吕,那么您可能需要一個 
 * TomcatEmbeddedServletContainerFactory(除非您已定義了自己的 EmbeddedInvletContainerFactory 
 * bean)。
 * <p>
 *
 * Auto-configuration classes are regular Spring {@link Configuration} beans. They are
 * located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
 * Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
 * often using {@link ConditionalOnClass @ConditionalOnClass} and
 * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
 * 
 * 自動配置類是 @Configuration 注解的bean颅痊。 它們使用 SpringFactoriesLoader 機制(針對這個類鍵入)殖熟。 
 * 通常,自動配置bean是 @Conditional bean(通常使用 @ConditionalOnClass 和
 * @ConditionalOnMissingBean 注釋)斑响。
 * 
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @see ConditionalOnBean
 * @see ConditionalOnMissingBean
 * @see ConditionalOnClass
 * @see AutoConfigureAfter
 * @see SpringBootApplication
 */
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

       String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

       Class<?>[] exclude() default {};

       String[] excludeName() default {};

}

這里引出了幾個新的注解菱属,@Import、@Conditional舰罚、@ConditionalOnClass纽门、@ConditionalOnMissingBean等,@EnableAutoConfiguration 注解的核心是@Import(EnableAutoConfigurationImportSelector.class)l里面導入的EnableAutoConfigurationImportSelector.class营罢。

/**
 * 核心方法赏陵,加載spring.factories文件中的 
 * org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置類
 */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                                                  AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        EnableAutoConfiguration.class, getBeanClassLoader());
    Assert.notEmpty(configurations,
           "No auto configuration classes found in META-INF/spring.factories. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

spring-boot-autoconfigure.jar 包中的 META-INF/spring.factories 里面默認配置了很多aoto-configuration,如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
......
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

例如 WebMvcAutoConfiguration.class:

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
        WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

    @Bean
    @ConditionalOnMissingBean(HttpPutFormContentFilter.class)
    @ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
    public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
        return new OrderedHttpPutFormContentFilter();
    }
    
    ......etc
}

引入這個類饲漾,相當于引入了一份webmvc的基本配置蝙搔,這個類跟其它很多類一樣,重度依賴于Spring Boot注釋考传。

總的來說吃型,@EnableAutoConfiguration完成了一下功能:

從classpath中搜尋所有的 META-INF/spring.factories 配置文件,并將其中org.springframework.boot.autoconfigure.EnableutoConfiguration 對應(yīng)的配置項通過反射實例化為對應(yīng)的標注了@Configuration的JavaConfig形式的IoC容器配置類僚楞,然后匯總為一個并加載到IoC容器勤晚。

2.5 @Import

相當于xml里面的<import/>,允許導入 Configuration注解類 泉褐、ImportSelector 和 ImportBeanDefinitionRegistrar的實現(xiàn)類赐写,以及普通的Component類。

2.6 @Conditional

Spring Boot的強大之處在于使用了 Spring 4 框架的新特性:@Conditional注釋膜赃,此注解使得只有在特定條件滿足時才啟用一些配置挺邀。這也 Spring Boot “智能” 的關(guān)鍵注解。Conditional大家族如下:

  • @ConditionalOnBean

  • @ConditionalOnClass

  • @ConditionalOnExpression

  • @ConditionalOnMissingBean

  • @ConditionalOnMissingClass

  • @ConditionalOnNotWebApplication

  • @ConditionalOnResource

  • @ConditionalOnWebApplication

以@ConditionalOnClass注解為例:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

    Class<?>[] value() default {};

    String[] name() default {};

}

核心實現(xiàn)類為OnClassCondition.class,這個注解實現(xiàn)類 Condition 接口:

public interface Condition {

    /**
     * 決定是否滿足條件的方法
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

2.7 總結(jié)

上面所有的注解都在做一件事:注冊bean到spring容器悠夯。他們通過不同的條件不同的方式來完成:

  • @SpringBootConfiguration 通過與 @Bean 結(jié)合完成Bean的 JavaConfig配置癌淮;
  • @ComponentScan 通過范圍掃描的方式,掃描特定注解注釋的類沦补,將其注冊到Spring容器乳蓄;
  • @EnableAutoConfiguration 通過 spring.factories 的配置,并結(jié)合 @Condition 條件夕膀,完成bean的注冊虚倒;
  • @Import 通過導入的方式,將指定的class注冊解析到Spring容器产舞;

3. Spring Boot 啟動流程

3.1 SpringApplication的實例化

下面開始分析關(guān)鍵方法:

SpringApplication.run(Application.class, args);

相應(yīng)實現(xiàn):

// 參數(shù)對應(yīng)的就是Application.class以及main方法中的args
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);
}

這里最終還是會構(gòu)造一個 SpringApplication 的實例魂奥,然后運行它的run方法。

思考:

1易猫、為什么要在靜態(tài)方法里面實例化 耻煤?

2、可不可以不實例化 准颓?

// 構(gòu)造實例
public SpringApplication(Object... sources) {
    initialize(sources);
}

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    // 推斷是否為web環(huán)境
    this.webEnvironment = deduceWebEnvironment();
    // 設(shè)置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    // 設(shè)置監(jiān)聽器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 推斷應(yīng)用入口類
    this.mainApplicationClass = deduceMainApplicationClass();
}

在構(gòu)造函數(shù)中哈蝇,主要做了4件事情:

3.1.1 推斷應(yīng)用類型是否是Web環(huán)境
// 相關(guān)常量
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };

private boolean deduceWebEnvironment() {
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return false;
        }
    }
    return true;
}

這里通過判斷是否存在 Servlet 和 ConfigurableWebApplicationContext 類來判斷是否是Web環(huán)境,上文提到的 @Conditional 注解也有基于 class 來判斷環(huán)境攘已, 所以在 Spring Boot 項目中 jar包 的引用不應(yīng)該隨意炮赦,不需要的依賴最好去掉。

3.1.2 設(shè)置初始化器(Initializer)
setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));

這里出現(xiàn)了一個概念 - 初始化器样勃。

先來看看代碼吠勘,再來嘗試解釋一下它是干嘛的:

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

// 這里的入?yún)ype就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // 使用Set保存names來去重 避免重復配置導致多次實例化
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 根據(jù)names來進行實例化
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    // 對實例進行排序 可用 Ordered接口 或 @Order注解 配置順序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

該方法會加載所有配置的 ApplicationContextInitializer 并進行實例化,加載 ApplicationContextInitializer 是在SpringFactoriesLoader.loadFactoryNames 方法里面進行的:

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  String factoryClassName = factoryClass.getName();

  try {
      Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
      ArrayList result = new ArrayList();

      while(urls.hasMoreElements()) {
          URL url = (URL)urls.nextElement();
          Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
          String factoryClassNames = properties.getProperty(factoryClassName);
          result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
      }

      return result;
  } catch (IOException var8) {
      throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
  }
}

這個方法會嘗試從類路徑的 META-INF/spring.factories 讀取相應(yīng)配置文件峡眶,然后進行遍歷剧防,讀取配置文件中Key為:org.springframework.context.ApplicationContextInitializer 的 value。以 spring-boot 這個包為例幌陕,它的 META-INF/spring.factories 部分定義如下所示:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer

因此這兩個類名會被讀取出來霹崎,然后放入到集合中惕味,準備開始下面的實例化操作:

// 關(guān)鍵參數(shù):
// type: org.springframework.context.ApplicationContextInitializer.class
// names: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<T>(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;
}

初始化步驟很直觀突委,類加載十拣,確認被加載的類確實是 org.springframework.context.ApplicationContextInitializer 的子類熏矿,然后就是得到構(gòu)造器進行初始化响巢,最后放入到實例列表中见妒。

因此供搀,所謂的初始化器就是 org.springframework.context.ApplicationContextInitializer 的實現(xiàn)類鞋囊,這個接口是這樣定義的:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    /**
     * Initialize the given application context.
     * @param applicationContext the application to configure
     */
    void initialize(C applicationContext);

}

ApplicationContextInitializer是一個回調(diào)接口止后,它會在 ConfigurableApplicationContext 容器 refresh() 方法調(diào)用之前被調(diào)用,做一些容器的初始化工作。

3.1.3. 設(shè)置監(jiān)聽器(Listener)

設(shè)置完了初始化器译株,下面開始設(shè)置監(jiān)聽器:

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

實現(xiàn)方式與Initializer一樣:

// 這里的入?yún)ype是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

同樣瓜喇,還是以spring-boot這個包中的 spring.factories 為例,看看相應(yīng)的 Key-Value :

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener

至于 ApplicationListener 接口歉糜,它是 Spring 框架中一個相當基礎(chǔ)的接口乘寒,代碼如下:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

這個接口基于JDK中的 EventListener 接口,實現(xiàn)了觀察者模式匪补。對于 Spring 框架的觀察者模式實現(xiàn)伞辛,它限定感興趣的事件類型需要是 ApplicationEvent 類型的子類,而這個類同樣是繼承自JDK中的 EventObject 類夯缺。

3.1.4. 推斷應(yīng)用入口類(Main)
this.mainApplicationClass = deduceMainApplicationClass();

這個方法的實現(xiàn)有點意思:

private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

它通過構(gòu)造一個運行時異常蚤氏,通過異常棧中方法名為main的棧幀來得到入口類的名字。

思考:

1踊兜、獲取堆棧信息的方式竿滨?

Thread.currentThread().getStackTrace();
new RuntimeException().getStackTrace();

至此捏境,對于SpringApplication實例的初始化過程就結(jié)束了于游。


3.2 SpringApplication.run方法

完成了實例化,下面開始調(diào)用run方法:

// 運行run方法
public ConfigurableApplicationContext run(String... args) {
    // 此類通常用于監(jiān)控開發(fā)過程中的性能典蝌,而不是生產(chǎn)應(yīng)用程序的一部分曙砂。
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    // 設(shè)置java.awt.headless系統(tǒng)屬性,默認為true 
    // Headless模式是系統(tǒng)的一種配置模式骏掀。在該模式下鸠澈,系統(tǒng)缺少了顯示設(shè)備、鍵盤或鼠標截驮。
    configureHeadlessProperty();

    // KEY 1 - 獲取SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);

    // 通知監(jiān)聽者笑陈,開始啟動
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);

        // KEY 2 - 根據(jù)SpringApplicationRunListeners以及參數(shù)來準備環(huán)境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        
        configureIgnoreBeanInfo(environment);

        // 準備Banner打印器 - 就是啟動Spring Boot的時候打印在console上的ASCII藝術(shù)字體
        Banner printedBanner = printBanner(environment);

        // KEY 3 - 創(chuàng)建Spring上下文
        context = createApplicationContext();

        // 注冊異常分析器
        analyzers = new FailureAnalyzers(context);

        // KEY 4 - Spring上下文前置處理
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);

        // KEY 5 - Spring上下文刷新
        refreshContext(context);

        // KEY 6 - Spring上下文后置處理
        afterRefresh(context, applicationArguments);

        // 發(fā)出結(jié)束執(zhí)行的事件
        listeners.finished(context, null);

        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, exceptionReporters, ex);
        throw new IllegalStateException(ex);
    }
}

這個run方法包含的內(nèi)容有點多的,根據(jù)上面列舉出的關(guān)鍵步驟逐個進行分析:

3.2.1 獲取RunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}

這里仍然利用了 getSpringFactoriesInstances 方法來獲取實例:

// 這里的入?yún)ⅲ?// type: SpringApplicationRunListener.class
// parameterTypes: new Class<?>[] { SpringApplication.class, String[].class };
// args: SpringApplication實例本身 + main方法傳入的args
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

所以這里還是從 META-INF/spring.factories 中讀取Key為 org.springframework.boot.SpringApplicationRunListener 的Values:

比如在spring-boot包中的定義的spring.factories:

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

我們來看看這個EventPublishingRunListener是干嘛的:

/**
 * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 */
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
  // ...
}

從類文檔可以看出葵袭,它主要是負責發(fā)布SpringApplicationEvent事件的涵妥,它會利用一個內(nèi)部的ApplicationEventMulticaster在上下文實際被刷新之前對事件進行處理。至于具體的應(yīng)用場景坡锡,后面用到的時候再來分析蓬网。

3.2.2 準備Environment環(huán)境
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 創(chuàng)建環(huán)境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 發(fā)布環(huán)境準備好的事件
    listeners.environmentPrepared(environment);
    // 非Web環(huán)境處理
    if (!this.webEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    return environment;
}

配置環(huán)境的方法:

protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

所以這里實際上包含了兩個步驟:

  1. 配置 Property Sources
  2. 配置 Profiles,為應(yīng)用程序環(huán)境配置哪些配置文件處于active(活動)狀態(tài)鹉勒。

對于Web應(yīng)用而言帆锋,得到的environment變量是一個StandardServletEnvironment的實例。得到實例后禽额,會調(diào)用前面RunListeners中的environmentPrepared方法:

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
            this.application, this.args, environment));
}

在這里锯厢,定義的廣播器就派上用場了皮官,它會發(fā)布一個 ApplicationEnvironmentPreparedEvent 事件。

那么有發(fā)布就有監(jiān)聽实辑,在構(gòu)建 SpringApplication 實例的時候不是初始化過一些 ApplicationListeners 嘛捺氢,其中的Listener就可能會監(jiān)聽ApplicationEnvironmentPreparedEvent事件,然后進行相應(yīng)處理剪撬。

所以這里 SpringApplicationRunListeners 的用途和目的也比較明顯了摄乒,它實際上是一個事件中轉(zhuǎn)器,它能夠感知到Spring Boot啟動過程中產(chǎn)生的事件婿奔,然后有選擇性的將事件進行中轉(zhuǎn)缺狠。為何是有選擇性的,看看它的實現(xiàn)就知道了:

@Override
public void contextPrepared(ConfigurableApplicationContext context) {

}

它的 contextPrepared 方法實現(xiàn)為空萍摊,沒有利用內(nèi)部的 initialMulticaster 進行事件的派發(fā)挤茄。因此即便是外部有 ApplicationListener 對這個事件有興趣,也是沒有辦法監(jiān)聽到的冰木。

那么既然有事件的轉(zhuǎn)發(fā),是誰在監(jiān)聽這些事件呢歇终,在這個類的構(gòu)造器中交待了:

public EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    for (ApplicationListener<?> listener : application.getListeners()) {
        this.initialMulticaster.addApplicationListener(listener);
    }
}

前面在構(gòu)建 SpringApplication 實例過程中設(shè)置的監(jiān)聽器在這里被逐個添加到了 initialMulticaster 對應(yīng)的 ApplicationListener 列表中匀钧。所以當 initialMulticaster 調(diào)用 multicastEvent 方法時佑刷,這些 Listeners 中定義的相應(yīng)方法就會被觸發(fā)了檀何。

3.2.3 創(chuàng)建Spring Context
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

// WEB應(yīng)用的上下文類型
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

// 非WEB應(yīng)用的上下文類型
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

AnnotationConfigEmbeddedWebApplicationContext 是個很重要的類周荐,以后再深入分析。

思考:ssm項目中有幾個上下文環(huán)境霎槐,Spring Boot中有幾個上下文環(huán)境丘跌,為什么袭景?

3.2.4 Spring Context前置處理
private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 將環(huán)境和上下文關(guān)聯(lián)起來
    context.setEnvironment(environment);

    // 為上下文配置Bean生成器以及資源加載器(如果它們非空)
    postProcessApplicationContext(context);

    // 調(diào)用初始化器
    applyInitializers(context);

    // 觸發(fā)Spring Boot啟動過程的contextPrepared事件
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // 添加兩個Spring Boot中的特殊單例Beans - springApplicationArguments以及springBootBanner
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // 加載sources - 對于DemoApplication而言,這里的sources集合只包含了它一個class對象
    Set<Object> sources = getSources();
    Assert.notEmpty(sources, "Sources must not be empty");

    // 加載動作 - 構(gòu)造BeanDefinitionLoader并完成Bean定義的加載
    load(context, sources.toArray(new Object[sources.size()]));

    // 觸發(fā)Spring Boot啟動過程的contextLoaded事件
    listeners.contextLoaded(context);
}

關(guān)鍵步驟:

配置Bean生成器以及資源加載器(如果它們非空):

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

調(diào)用初始化器

protected void applyInitializers(ConfigurableApplicationContext context) {
    // Initializers是經(jīng)過排序的
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

這里終于用到了在創(chuàng)建 SpringApplication實例 時設(shè)置的初始化器了碍岔,依次對它們進行遍歷浴讯,并調(diào)用initialize方法。

3.2.5 Spring Context刷新 ??
private void refreshContext(ConfigurableApplicationContext context) {
  // 由于這里需要調(diào)用父類一系列的refresh操作蔼啦,涉及到了很多核心操作榆纽,因此耗時會比較長,本文不做具體展開
    refresh(context);

    // 注冊一個關(guān)閉容器時的鉤子函數(shù)
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

// 調(diào)用父類的refresh方法完成容器刷新的基礎(chǔ)操作
protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext)applicationContext).refresh();
}

注冊關(guān)閉容器時的鉤子函數(shù)的默認實現(xiàn)是在 AbstractApplicationContext 類中:

public void registerShutdownHook() {
  if(this.shutdownHook == null) {
    this.shutdownHook = new Thread() {
      public void run() {
        synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
          AbstractApplicationContext.this.doClose();
        }
      }
    };
    Runtime.getRuntime().addShutdownHook(this.shutdownHook);
  }
}

如果沒有提供自定義的shutdownHook捏肢,那么會生成一個默認的奈籽,并添加到Runtime中。默認行為就是調(diào)用它的doClose方法鸵赫,完成一些容器銷毀時的清理工作衣屏。

3.2.6 Spring Context后置處理
protected void afterRefresh(ConfigurableApplicationContext context,
            ApplicationArguments args) {
    callRunners(context, args);
}

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

private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
    try {
        (runner).run(args);
    }
    catch (Exception ex) {
        throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
    }
}

private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
    try {
        (runner).run(args.getSourceArgs());
    }
    catch (Exception ex) {
        throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
    }
}

所謂的后置操作,就是在容器完成刷新后辩棒,依次調(diào)用注冊的Runners狼忱。Runners可以是兩個接口的實現(xiàn)類:

  • org.springframework.boot.ApplicationRunner
  • org.springframework.boot.CommandLineRunner

CommandLineRunner膨疏、ApplicationRunner 接口是在容器啟動成功后的最后一步回調(diào)(類似開機自啟動)

這兩個接口有什么區(qū)別呢:

public interface ApplicationRunner {

    void run(ApplicationArguments args) throws Exception;

}

public interface CommandLineRunner {

    void run(String... args) throws Exception;

}

其實沒有什么不同之處,除了接口中的run方法接受的參數(shù)類型是不一樣的以外钻弄。一個是封裝好的 ApplicationArguments 類型佃却,另一個是直接的String不定長數(shù)組類型。因此根據(jù)需要選擇相應(yīng)的接口實現(xiàn)即可窘俺。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饲帅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瘤泪,更是在濱河造成了極大的恐慌灶泵,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件对途,死亡現(xiàn)場離奇詭異赦邻,居然都是意外死亡,警方通過查閱死者的電腦和手機掀宋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門深纲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人劲妙,你說我怎么就攤上這事湃鹊。” “怎么了镣奋?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵币呵,是天一觀的道長。 經(jīng)常有香客問我侨颈,道長余赢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任哈垢,我火速辦了婚禮妻柒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耘分。我一直安慰自己举塔,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布求泰。 她就那樣靜靜地躺著央渣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渴频。 梳的紋絲不亂的頭發(fā)上芽丹,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音卜朗,去河邊找鬼拔第。 笑死咕村,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的楼肪。 我是一名探鬼主播培廓,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼春叫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泣港,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤暂殖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后当纱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呛每,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年坡氯,在試婚紗的時候發(fā)現(xiàn)自己被綠了晨横。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡箫柳,死狀恐怖手形,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悯恍,我是刑警寧澤库糠,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站涮毫,受9級特大地震影響瞬欧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罢防,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一艘虎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咒吐,春花似錦野建、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至妄呕,卻和暖如春陶舞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绪励。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工肿孵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留唠粥,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓停做,卻偏偏與公主長得像晤愧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蛉腌,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,809評論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理官份,服務(wù)發(fā)現(xiàn),斷路器烙丛,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 1.1 spring IoC容器和beans的簡介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器舅巷,...
    simoscode閱讀 6,713評論 2 22
  • 入門 介紹 Spring Boot Spring Boot 使您可以輕松地創(chuàng)建獨立的、生產(chǎn)級的基于 Spring ...
    Hsinwong閱讀 16,888評論 2 89
  • 很多年沒有回家過年了河咽,今年回老家過年钠右,回頭看,發(fā)現(xiàn)自己和以前還是有一些變化忘蟹§浚總結(jié)如下: 一.更知道自己心里要的是什...
    何梵靜Eveleen閱讀 817評論 0 13