阅读 290

SpringBoot原理解析

第一章 SpringBoot介绍

SpringBoot是Spring项目中的一个子项目,因为Spring在bean配置上较为复杂,所以引入SpringBoot框架目的是用“约定大于配置”的思想来轻量化Spring的配置文件。

何谓“约定大于配置”,就是将需要整合一些组件的默认的一些配置提取出来,先约定好,这样在整合组件的时候如果不配置这些属性就会使用默认的配置,这样就省去了很多不必要的配置。

SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式

1.1 SpringBoot的优势

  1. 为基于Spring的开发提供更快的入门体验
  2. 开箱即用,没有代码生成,也无需XML配置。同时也可以修改默认值来满足特定的需求
  3. 提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等
  4. SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式
  5. 提供默认的版本控制使得在构建项目的时候引用组件免去了考虑以来版本的问题
  6. 启动方便,直接通过主方法就能启动

1.2 SpringBoot的版本控制

截止当前(2021.3)SpringBoot的先行发行版本为2.4.X

1.2.1 较之前本本区别如下:

特定于Logback的日志记录属性已重命名,以反映它们特定于Logback的事实。以前的名称已被弃用。

以下Spring Boot属性已更改:

  • logging.pattern.rolling-file-name → logging.logback.rollingpolicy.file-name-pattern
  • logging.file.clean-history-on-start → logging.logback.rollingpolicy.clean-history-on-start
  • logging.file.max-size → logging.logback.rollingpolicy.max-file-size
  • logging.file.total-size-cap → logging.logback.rollingpolicy.total-size-cap
  • logging.file.max-history → logging.logback.rollingpolicy.max-history

以及它们映射到的系统环境属性:

  • ROLLING_FILE_NAME_PATTERN → LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN
  • LOG_FILE_CLEAN_HISTORY_ON_START → LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START
  • LOG_FILE_MAX_SIZE → LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE
  • LOG_FILE_TOTAL_SIZE_CAP → LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP
  • LOG_FILE_MAX_HISTORY → LOGBACK_ROLLINGPOLICY_MAX_HISTORY

默认情况下,HTTP跟踪不再包含Cookie标头

Cookie``Set-Cookie默认情况下,HTTP跟踪中不再包含请求标头和响应标头。要恢复Spring Boot 2.3的行为,请设置management.trace.http.includecookies, errors, request-headers, response-headers

1.2.2 更多版本

更多的历史版本变更可以参照右边链接>>>

第二章 SpringBoot包扫描原理

2.1 SpringBootApplication注解详解

如何创建 SpringBoot 应用我就不多提了吧,过程非常简单。如果通过 IDEA/eclipse 的 SpringInitializer 创建就更简单了。这里我选择使用 SpringInitializer 来快速创建 SpringBoot 应用。

入门程序中,pom文件我只引入了 spring-boot-starter-web

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
复制代码

下面我们先来编写一个 SpringBoot 的主启动类:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
复制代码

这可能是一个最简单的 SpringBoot 应用启动引导类了,运行主启动类的main方法就可以启动 SpringBoot 应用。

SpringBootApplication注解是一个组合注解,由@ComponentScan、@SpringBootConfiuration、@EnableAutoConfiguration组成,如下图所示:

img

2.1.1 @ComponentScan注解

扫描和过滤项目中配置的Bean以及自动配置类过滤,其有两个过滤器:TypeExcludeFilter和AutoConfigurationExcludeFilter

  • TypeExcludeFilter:提供从 BeanFactory 加载并自动应用于 @SpringBootApplication 扫描的排除 TypeFilter 。注意,TypeExcludeFilters 在应用程序生命周期的很早就初始化了,它们通常不应该依赖于任何其他bean。它们主要在内部用于支持 spring-boot-test

  • AutoConfigurationExcludeFilter:用于过滤配置类和自动配置类,它的 match 方法要判断两个部分:是否是一个配置类,是否是一个自动配置类。其实光从方法名上也就看出来了,下面的方法是其调用实现,里面有一个很关键的机制:SpringFactoriesLoader.loadFactoryNames

2.1.2 @SpringBootConfiguration注解

标识一个类作为 SpringBoot 的配置类,它可以是Spring原生的 @Configuration 的一种替换方案,目的是这个配置可以被自动发现。

应用应当只在主启动类上标注 @SpringBootConfiguration,大多数情况下都是直接使用 @SpringBootApplication

第三章 SpringBoot的自动装配

SpringBoot的自动装配的入口是@SpringBootApplication组合注解中的注解@EnableAutoConfiguration该注解开启了SpringBoot的自动装配的功能。下面详细介绍SpringBoot是如何实现自动装配的。

3.1.1 Spring的手动装配原理

说到自动装配,首先要了解一下Spring的手动装配是如何实现的。 在原生Spring中实现手动装配主要靠一下三个注解:

  • 使用模式注解 @Component 等(Spring2.5+)
  • 使用配置类 @Configuration 与 @Bean (Spring3.0+)
  • 使用模块装配 @EnableXXX 与 @Import (Spring3.1+)

其中使用 @Component 及衍生注解很常见,咱开发中常用的套路,不再赘述。

一个被@Configuration注解的类,其实就是就是对应的Spring中的ApplicationContext.xml的配置文件,互相可以被替换。其中被@Bean注解的方法就是一个bean的配置。但这种方式一旦注册过多,会导致编码成本高,维护不灵活等问题。

SpringFramework 提供了模块装配功能,通过给配置类标注 @EnableXXX 注解,再在注解上标注 @Import 注解,即可完成组件装配的效果。下面介绍模块装配的使用方式。

3.1.2 @EnableXXX与@Import的使用

我们可以新建一个一个@EnableCar的注解如下,代表其可以开启汽车的组件:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EnableCar {
    
}
复制代码

@Import 可以传入四种类型:普通类、配置类、ImportSelector 的实现类,ImportBeanDefinitionRegistrar 的实现类。具体如文档注释中描述:

public @interface Import {

	/**
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
	Class<?>[] value();

}
复制代码

value中写的很明白了,可以导入配置类、ImportSelector 的实现类,ImportBeanDefinitionRegistrar 的实现类,或者普通类。 下面介绍 @Import 的用法。

3.1.2.1 通过@Import导入普通类

@Import({Red.class})
public @interface EnableColor {
    
}
复制代码

该普通类不用任何注解的修饰,可以是一个一般的POJO类。

3.1.2.2 通过@Import导入配置类

新建一个配置类,代码如下:

@Configuration
public class CarRegistrarConfiguration {
    
    @Bean
    public Weilai Weilai() {
        return new Weilai();
    }
    
}
复制代码

之后在 @EnableColor 的 @Import 注解中加入 CarRegistrarConfiguration:

@Import({RongWei.class, CarRegistrarConfiguration.class}) public @interface EnableColor {

}

3.1.2.3 通过ImportSelector导入

新建 CarImportSelector,实现 ImportSelector 接口,代码如下:

public class CarImportSelector implements ImportSelector {
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {BenChi.class.getName(), BaoMa.class.getName()};
    }
    
}
复制代码

3.1.2.4 通过ImportBeanDefinitionRegistrar导入

新建 CarImportBeanDefinitionRegistrar,实现 ImportBeanDefinitionRegistrar 接口:

public class CarImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("audi", new RootBeanDefinition(Audi.class));
    }
    
}
复制代码

之后在 @EnableColor 的 @Import 注解中加入 ColorImportBeanDefinitionRegistrar:

@Import({RongWei.class, CarRegistrarConfiguration.class, CarImportSelector.class, CarImportBeanDefinitionRegistrar.class})
public @interface EnableColor {
    
}
复制代码

3.1.3 SpringBoot的自动装配原理

以上就是Spring通过手动装配的四种方法,那么SpringBoot中又是如何运用这些方法实现自动装配的呢?其核心的注解就是@EnableAutoConfiguration此注解中又导入了两个注解@Import(AutoConfigurationImportSelector.class)注解和@AutoConfigurationPackage注解,下面来分别阐述这两个注解的作用

3.1.3.1 @AutoConfigurationPackage

源码如下:

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage
复制代码

从一开始学 SpringBoot 就知道一件事:主启动类必须放在所有自定义组件的包的最外层,以保证Spring能扫描到它们。由此可知是它起的作用。

它的实现原理是在注解上标注了 @Import,导入了一个 AutoConfigurationPackages.Registrar 。

AutoConfigurationPackages.Registrar的作用是:把主配置所在根包保存起来以便后期扫描用。

在保存了所有Packages信息后,当有一个自动装配的组件注册进来就可以到这些packages中去扫描有没有被注册到Spring容器的启用组件的类,以此来判断是否要自动装配该组件,实例使用Mybatis自动装配类来描述:

引入 mybatis-spring-boot-starter 依赖后,可以在 IDEA 中打开 MyBatisAutoConfiguration 类。在这个配置类中,咱可以找到这样一个组件:AutoConfiguredMapperScannerRegistrar

public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (!AutoConfigurationPackages.has(this.beanFactory)) {
            logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
            return;
        }
        logger.debug("Searching for mappers annotated with @Mapper");

        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        // logger ......
        // 注册Mapper ......
    }
复制代码

看类名也能看的出来,它是扫描 Mapper 并注册到 IOC 容器的 ImportBeanDefinitionRegistrar !那这里头,取扫描根包的动作就是 AutoConfigurationPackages.get(this.beanFactory) ,由此就可以把事先准备好的 basePackages 都拿出来,之后进行扫描。

到这里,就呼应了文档注释中的描述,也解释了为什么 SpringBoot 的启动器一定要在所有类的最外层。

3.1.3.2 @AutoConfigurationImportSelector

此注解又实现了如下的接口:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered
复制代码
  • DeferredImportSelector:DeferredImportSelector 的执行时机,是在 @Configuration 注解中的其他逻辑被处理完毕之后(包括对 @ImportResource、@Bean 这些注解的处理)再执行,换句话说,DeferredImportSelector 的执行时机比 ImportSelector 更晚。

AutoConfigurationImportSelector,它的核心部分,就是 ImportSelector 的 selectImport 方法:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
    // 加载自动配置类
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, 
            annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
复制代码

关键的源码在 getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata) :

/**
 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
 * of the importing {@link Configuration @Configuration} class.
 * 
 * 根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry。
 */
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
         AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    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 new AutoConfigurationEntry(configurations, exclusions);
}
复制代码

这个方法又调用了 SpringFactoriesLoader.loadFactoryNames 方法,传入的Class就是 @EnableAutoConfiguration:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    //     ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
        Enumeration<URL> urls = (classLoader != null ?
                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                       FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
复制代码

源码中使用 classLoader 去加载了指定常量路径下的资源: FACTORIES_RESOURCE_LOCATION ,而这个常量指定的路径实际是:META-INF/spring.factories 。

这个文件在 spring-boot-autoconfiguration 包下可以找到。

spring-boot-autoconfiguration 包下 META-INF/spring.factories 节选:

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

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
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,\
......
复制代码

之后拿到这个资源文件,以 Properties 的形式加载,并取出 org.springframework.boot.autoconfigure.EnableAutoConfiguration 指定的所有自动配置类(是一个很大的字符串,里面都是自动配置类的全限定类名),装配到IOC容器中,之后自动配置类就会通过 ImportSelector 和 @Import 的机制被创建出来,之后就生效了。

这也就解释了为什么 即便没有任何配置文件,SpringBoot的Web应用都能正常运行。

所以如果我们需要添加自己的自动配置组件,就需要在自定义的自动装配项目添加META-INF/spring.factories如下文件,在文件中注册上自己的自动装配配置类。

第四章 SpringBoot的入口run方法解读

先对本篇内容有个整体了解:

img

4.1 main方法进入

从最简单的入门程序开始:

@SpringBootApplication
public class DemoApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
    
}
复制代码

4.2 进入SpringApplication.run方法

进入run方法,可以发现执行的 SpringBoot 应用启动操作分为两步:

    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);
    }
复制代码

从代码中我们可以看到run方法最终返回的是Spring的上下文即ApplicationContext而这个上下文是ConfigurableApplicationContext说明获取的是通过@Configuration注入的Bean

4.3 new SpringApplication(primarySources):创建SpringApplication

最终调用的构造方法是下面的两参数方法。

private Set<Class<?>> primarySources;

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

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // resourceLoader为null
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 将传入的DemoApplication启动类放入primarySources中,这样应用就知道主启动类在哪里,叫什么了
    // SpringBoot一般称呼这种主启动类叫primarySource(主配置资源来源)
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 3.1 判断当前应用环境
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 3.2 设置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 3.3 设置监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 3.4 确定主配置类
    this.mainApplicationClass = deduceMainApplicationClass();
}
复制代码

Create a new SpringApplication instance. The application context will load beans from the specified primary sources (see class-level documentation for details. The instance can be customized before calling run(String...).

创建一个新的 SpringApplication 实例。应用程序上下文将从指定的主要源加载Bean(有关详细信息,请参见类级别的文档)。可以在调用run(String...)之前自定义实例。

文档中描述可以在run方法之前自定义实例,换句话说,可以手动配置一些 SpringApplication 的属性。

4.3.1 自定义SpringApplication

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(DemoApplication.class);
        springApplication.setWebApplicationType(WebApplicationType.SERVLET); //强制使用WebMvc环境
        springApplication.setBannerMode(Banner.Mode.OFF); //不打印Banner
        springApplication.run(args);
    }

}
复制代码

下面对 SpringApplication 的构造方法实现中每一步作详细解析:

4.3.2 WebApplicationType.deduceFromClasspath:判断当前应用环境

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

复制代码

这个方法没有文档注释,但方法名和返回值类型已经可以描述方法用途:从classpath下判断当前SpringBoot应用应该使用哪种环境启动

上面的代码块中我把一些这个类中定义的常量也贴了进来,方便小伙伴们阅读。它们是描述了一些 Servlet 的全限定名、DispatcherServlet 的全限定名等等,它们的用途是配合下面的方法判断应用的classpath里是否有这些类

下面的方法实现中:

  • 第一个if结构先判断是否是 Reactive 环境,发现有 WebFlux 的类但没有 WebMvc 的类,则判定为 Reactive 环境(全NIO)
  • 之后的for循环要检查是否有跟 Servlet 相关的类,如果有任何一个类没有,则判定为非Web环境
  • 如果for循环走完了,证明所有类均在当前 classpath 下,则为 Servlet(WebMvc) 环境

4.3 setInitializers:设置初始化器

setInitializers方法会将一组类型为 ApplicationContextInitializer 的初始化器放入 SpringApplication 中。

而这组 ApplicationContextInitializer,是在构造方法中,通过 getSpringFactoriesInstances 得到的。

在阅读这部分源码之前,先来了解一下 ApplicationContextInitializer 是什么。

4.3.1 【重要】ApplicationContextInitializer

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext>
复制代码

文档注释原文翻译:

Callback interface for initializing a Spring ConfigurableApplicationContext prior to being refreshed. Typically used within web applications that require some programmatic initialization of the application context. For example, registering property sources or activating profiles against the context's environment. See ContextLoader and FrameworkServlet support for declaring a "contextInitializerClasses" context-param and init-param, respectively. ApplicationContextInitializer processors are encouraged to detect whether Spring's Ordered interface has been implemented or if the @Order annotation is present and to sort instances accordingly if so prior to invocation.

用于在刷新容器之前初始化Spring ConfigurableApplicationContext 的回调接口。

通常在需要对应用程序上下文进行某些编程初始化的Web应用程序中使用。例如,根据上下文环境注册属性源或激活配置文件。请参阅 ContextLoaderFrameworkServlet 支持,分别声明 "contextInitializerClasses" 的 context-param 和 init-param。

鼓励 ApplicationContextInitializer 处理器检测是否已实现Spring的 Ordered 接口,或者是否标注了 @Order 注解,并在调用之前相应地对实例进行排序。

第一句注释已经解释的很明白了,它是在IOC容器之前的回调。

4.3.2 SpringFactoriesLoader.loadFactoryNames

这个方法我们已经在之前详细解析过,这里不重复解释,不过我们可以看一眼 spring-bootspring-boot-autoconfigure 包下的 spring.factories 里面对于 ApplicationContextInitializer 的配置:

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
复制代码
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
复制代码

它一共配置了6个 ApplicationContextInitializer,对这些Initializer作简单介绍:

  • ConfigurationWarningsApplicationContextInitializer:报告IOC容器的一些常见的错误配置
  • ContextIdApplicationContextInitializer:设置Spring应用上下文的ID
  • DelegatingApplicationContextInitializer:加载 application.propertiescontext.initializer.classes 配置的类
  • ServerPortInfoApplicationContextInitializer:将内置servlet容器实际使用的监听端口写入到 Environment 环境属性中
  • SharedMetadataReaderFactoryContextInitializer:创建一个 SpringBoot 和 ConfigurationClassPostProcessor 共用的 CachingMetadataReaderFactory 对象
  • ConditionEvaluationReportLoggingListener:将 ConditionEvaluationReport 写入日志

4.3.3 createSpringFactoriesInstances:反射创建这些组件的实例

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;
}
复制代码

4.4 setListeners:设置监听器

与上面一样,先了解下 ApplicationListener

4.4.1 【重要】ApplicationListener

import java.util.EventListener;

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener
复制代码

它的文档注释原文翻译:

Interface to be implemented by application event listeners. Based on the standard java.util.EventListener interface for the Observer design pattern. As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.

由应用程序事件监听器实现的接口。基于观察者模式的标准 java.util.EventListener 接口。

从Spring 3.0开始,ApplicationListener 可以一般性地声明监听的事件类型。向IOC容器注册后,将相应地过滤事件,并且仅针对匹配事件对象调用监听器。

文档注释也写的很明白,它就是监听器,用于监听IOC容器中发布的各种事件。至于事件是干嘛的,要到后续看IOC容器的刷新过程时才能看到。

4.4.2 加载Listener

/ 加载所有类型为ApplicationListener的已配置的组件的全限定类名
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
复制代码

套路与 setInitializers 一致,同样的我们来看看它加载了的 Listener:

# 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.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
复制代码
  • ClearCachesApplicationListener:应用上下文加载完成后对缓存做清除工作
  • ParentContextCloserApplicationListener:监听双亲应用上下文的关闭事件并往自己的子应用上下文中传播
  • FileEncodingApplicationListener:检测系统文件编码与应用环境编码是否一致,如果系统文件编码和应用环境的编码不同则终止应用启动
  • AnsiOutputApplicationListener:根据 spring.output.ansi.enabled 参数配置 AnsiOutput
  • ConfigFileApplicationListener:从常见的那些约定的位置读取配置文件
  • DelegatingApplicationListener:监听到事件后转发给 application.properties 中配置的 context.listener.classes 的监听器
  • ClasspathLoggingApplicationListener:对环境就绪事件 ApplicationEnvironmentPreparedEvent 和应用失败事件 ApplicationFailedEvent 做出响应
  • LoggingApplicationListener:配置 LoggingSystem。使用 logging.config 环境变量指定的配置或者缺省配置
  • LiquibaseServiceLocatorApplicationListener:使用一个可以和 SpringBoot 可执行jar包配合工作的版本替换 LiquibaseServiceLocator
  • BackgroundPreinitializer:使用一个后台线程尽早触发一些耗时的初始化任务

4.4.3 deduceMainApplicationClass:确定主配置类

private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            // 从本方法开始往上爬,哪一层调用栈上有main方法,方法对应的类就是主配置类
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}
复制代码

源码很简单,从 deduceMainApplicationClass 方法开始往上爬,哪一层调用栈上有main方法,方法对应的类就是主配置类,就返回这个类。

文章分类
后端
文章标签