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类并且触发自动配置(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();
    }
}

总结,@Configuration相当于一个spring的xml文件,配合@Bean注解,可以在里面配置需要Spring容器管理的bean。

2.3 @ComponentScan

@ComponentScan源码:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    /**
     * 对应的包扫描路径 可以是单个路径,也可以是扫描的路径数组
     * @return
     */
    @AliasFor("basePackages")
    String[] value() default {};
    /**
     * 和value一样是对应的包扫描路径 可以是单个路径,也可以是扫描的路径数组
     * @return
     */
    @AliasFor("value")
    String[] basePackages() default {};
    /**
     * 指定具体的扫描类
     * @return
     */
    Class<?>[] basePackageClasses() default {};
    /**
     * 对应的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 {
    
}

总结:@ComponentScan通常与@Configuration一起配合使用,相当于xml里面的<context:component-scan>,用来告诉Spring需要扫描哪些包或类。如果不设值的话默认扫描@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应用程序上下文的自动配置,试图猜测和配置您可能需要的bean。 自动配置类通常基于您的类路径和您
 * 定义的bean应用。 例如,如果您在类路径中有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 对应的配置项通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。

2.5 @Import

相当于xml里面的<import/>,允许导入 Configuration注解类 、ImportSelector 和 ImportBeanDefinitionRegistrar的实现类,以及普通的Component类。

2.6 @Conditional

Spring Boot的强大之处在于使用了 Spring 4 框架的新特性:@Conditional注释,此注解使得只有在特定条件满足时才启用一些配置。这也 Spring Boot “智能” 的关键注解。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 {};

}

核心实现类为OnClassCondition.class,这个注解实现类 Condition 接口:

public interface Condition {

    /**
     * 决定是否满足条件的方法
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

2.7 总结

上面所有的注解都在做一件事:注册bean到spring容器。他们通过不同的条件不同的方式来完成:

  • @SpringBootConfiguration 通过与 @Bean 结合完成Bean的 JavaConfig配置;
  • @ComponentScan 通过范围扫描的方式,扫描特定注解注释的类,将其注册到Spring容器;
  • @EnableAutoConfiguration 通过 spring.factories 的配置,并结合 @Condition 条件,完成bean的注册;
  • @Import 通过导入的方式,将指定的class注册解析到Spring容器;

3. Spring Boot 启动流程

3.1 SpringApplication的实例化

下面开始分析关键方法:

SpringApplication.run(Application.class, args);

相应实现:

// 参数对应的就是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);
}

这里最终还是会构造一个 SpringApplication 的实例,然后运行它的run方法。

思考:

1、为什么要在静态方法里面实例化 ?

2、可不可以不实例化 ?

// 构造实例
public SpringApplication(Object... sources) {
    initialize(sources);
}

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    // 推断是否为web环境
    this.webEnvironment = deduceWebEnvironment();
    // 设置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    // 设置监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 推断应用入口类
    this.mainApplicationClass = deduceMainApplicationClass();
}

在构造函数中,主要做了4件事情:

3.1.1 推断应用类型是否是Web环境
// 相关常量
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环境,上文提到的 @Conditional 注解也有基于 class 来判断环境, 所以在 Spring Boot 项目中 jar包 的引用不应该随意,不需要的依赖最好去掉。

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

这里出现了一个概念 - 初始化器。

先来看看代码,再来尝试解释一下它是干嘛的:

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

// 这里的入参type就是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));
    // 根据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 读取相应配置文件,然后进行遍历,读取配置文件中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

因此这两个类名会被读取出来,然后放入到集合中,准备开始下面的实例化操作:

// 关键参数:
// 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 的子类,然后就是得到构造器进行初始化,最后放入到实例列表中。

因此,所谓的初始化器就是 org.springframework.context.ApplicationContextInitializer 的实现类,这个接口是这样定义的:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

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

}

ApplicationContextInitializer是一个回调接口,它会在 ConfigurableApplicationContext 容器 refresh() 方法调用之前被调用,做一些容器的初始化工作。

3.1.3. 设置监听器(Listener)

设置完了初始化器,下面开始设置监听器:

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

实现方式与Initializer一样:

// 这里的入参type是: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 为例,看看相应的 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 框架中一个相当基础的接口,代码如下:

@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 接口,实现了观察者模式。对于 Spring 框架的观察者模式实现,它限定感兴趣的事件类型需要是 ApplicationEvent 类型的子类,而这个类同样是继承自JDK中的 EventObject 类。

3.1.4. 推断应用入口类(Main)
this.mainApplicationClass = deduceMainApplicationClass();

这个方法的实现有点意思:

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

它通过构造一个运行时异常,通过异常栈中方法名为main的栈帧来得到入口类的名字。

思考:

1、获取堆栈信息的方式?

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

至此,对于SpringApplication实例的初始化过程就结束了。


3.2 SpringApplication.run方法

完成了实例化,下面开始调用run方法:

// 运行run方法
public ConfigurableApplicationContext run(String... args) {
    // 此类通常用于监控开发过程中的性能,而不是生产应用程序的一部分。
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

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

    // 设置java.awt.headless系统属性,默认为true 
    // Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。
    configureHeadlessProperty();

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

    // 通知监听者,开始启动
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);

        // KEY 2 - 根据SpringApplicationRunListeners以及参数来准备环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        
        configureIgnoreBeanInfo(environment);

        // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
        Banner printedBanner = printBanner(environment);

        // KEY 3 - 创建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);

        // 发出结束执行的事件
        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方法包含的内容有点多的,根据上面列举出的关键步骤逐个进行分析:

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 方法来获取实例:

// 这里的入参:
// 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 {
  // ...
}

从类文档可以看出,它主要是负责发布SpringApplicationEvent事件的,它会利用一个内部的ApplicationEventMulticaster在上下文实际被刷新之前对事件进行处理。至于具体的应用场景,后面用到的时候再来分析。

3.2.2 准备Environment环境
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 创建环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // 发布环境准备好的事件
    listeners.environmentPrepared(environment);
    // 非Web环境处理
    if (!this.webEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    return environment;
}

配置环境的方法:

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

所以这里实际上包含了两个步骤:

  1. 配置 Property Sources
  2. 配置 Profiles,为应用程序环境配置哪些配置文件处于active(活动)状态。

对于Web应用而言,得到的environment变量是一个StandardServletEnvironment的实例。得到实例后,会调用前面RunListeners中的environmentPrepared方法:

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

在这里,定义的广播器就派上用场了,它会发布一个 ApplicationEnvironmentPreparedEvent 事件。

那么有发布就有监听,在构建 SpringApplication 实例的时候不是初始化过一些 ApplicationListeners 嘛,其中的Listener就可能会监听ApplicationEnvironmentPreparedEvent事件,然后进行相应处理。

所以这里 SpringApplicationRunListeners 的用途和目的也比较明显了,它实际上是一个事件中转器,它能够感知到Spring Boot启动过程中产生的事件,然后有选择性的将事件进行中转。为何是有选择性的,看看它的实现就知道了:

@Override
public void contextPrepared(ConfigurableApplicationContext context) {

}

它的 contextPrepared 方法实现为空,没有利用内部的 initialMulticaster 进行事件的派发。因此即便是外部有 ApplicationListener 对这个事件有兴趣,也是没有办法监听到的。

那么既然有事件的转发,是谁在监听这些事件呢,在这个类的构造器中交待了:

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

前面在构建 SpringApplication 实例过程中设置的监听器在这里被逐个添加到了 initialMulticaster 对应的 ApplicationListener 列表中。所以当 initialMulticaster 调用 multicastEvent 方法时,这些 Listeners 中定义的相应方法就会被触发了。

3.2.3 创建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应用的上下文类型
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

// 非WEB应用的上下文类型
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

AnnotationConfigEmbeddedWebApplicationContext 是个很重要的类,以后再深入分析。

思考:ssm项目中有几个上下文环境,Spring Boot中有几个上下文环境,为什么?

3.2.4 Spring Context前置处理
private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 将环境和上下文关联起来
    context.setEnvironment(environment);

    // 为上下文配置Bean生成器以及资源加载器(如果它们非空)
    postProcessApplicationContext(context);

    // 调用初始化器
    applyInitializers(context);

    // 触发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");

    // 加载动作 - 构造BeanDefinitionLoader并完成Bean定义的加载
    load(context, sources.toArray(new Object[sources.size()]));

    // 触发Spring Boot启动过程的contextLoaded事件
    listeners.contextLoaded(context);
}

关键步骤:

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

调用初始化器

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

这里终于用到了在创建 SpringApplication实例 时设置的初始化器了,依次对它们进行遍历,并调用initialize方法。

3.2.5 Spring Context刷新 🚩
private void refreshContext(ConfigurableApplicationContext context) {
  // 由于这里需要调用父类一系列的refresh操作,涉及到了很多核心操作,因此耗时会比较长,本文不做具体展开
    refresh(context);

    // 注册一个关闭容器时的钩子函数
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

// 调用父类的refresh方法完成容器刷新的基础操作
protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext)applicationContext).refresh();
}

注册关闭容器时的钩子函数的默认实现是在 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中。默认行为就是调用它的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);
    }
}

所谓的后置操作,就是在容器完成刷新后,依次调用注册的Runners。Runners可以是两个接口的实现类:

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

CommandLineRunner、ApplicationRunner 接口是在容器启动成功后的最后一步回调(类似开机自启动)

这两个接口有什么区别呢:

public interface ApplicationRunner {

    void run(ApplicationArguments args) throws Exception;

}

public interface CommandLineRunner {

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

}

其实没有什么不同之处,除了接口中的run方法接受的参数类型是不一样的以外。一个是封装好的 ApplicationArguments 类型,另一个是直接的String不定长数组类型。因此根据需要选择相应的接口实现即可。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,724评论 6 342
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 1.1 spring IoC容器和beans的简介 Spring 框架的最核心基础的功能是IoC(控制反转)容器,...
    simoscode阅读 6,699评论 2 22
  • 入门 介绍 Spring Boot Spring Boot 使您可以轻松地创建独立的、生产级的基于 Spring ...
    Hsinwong阅读 16,846评论 2 89
  • 很多年没有回家过年了,今年回老家过年,回头看,发现自己和以前还是有一些变化。总结如下: 一.更知道自己心里要的是什...
    何梵静Eveleen阅读 768评论 0 13