Spring Boot 自动配置原理

Table of Contents

  1. 前言
  2. SpringApplication
  3. Spring Boot AutoConfigure
  4. 条件注解
  5. WebApplicationType
  6. @SpringBootApplication
  7. 简单总结
  8. 结语

前言

在学习了 Spring Framework 一段时间后我便开始了 Spring Boot 的学习,在写下一段简单的代码后,我的程序成功的运行了起来。

但随之而来的不是其简便带来的喜悦,而是是对其原理的困惑与慌张,此时,Spring Boot 在手里就像是一个脱离了自己控制的工具,它存在了一部分在你控制之外的行为,而且你还不知道这样的行为是如何发生的。

我不喜欢这样的感觉,于是我便去查询了相关的资料,解答了自己的疑问。这样一来,脱离了自己控制的那一部分,仿佛又回到了自己的控制之中。

SpringApplication

通常情况下,我们的应用程序可以通过 SpringApplication.run(Application.class) 启动,所以,我们可以来看一下这个方法到底干了什么:

public class SpringApplication {
  // -----------------------------------------------------------------------------------------------
  // SpringApplication.run(...)
  // -----------------------------------------------------------------------------------------------
  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(...)
  // -----------------------------------------------------------------------------------------------
  public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
  }

  public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
  }

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

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

上面的代码是和 Spring Boot 自动配置密切相关的部分代码,我们可以看到,当我们调用 SpringApplication.run(...) 方法后会构造一个 SpringApplication 实例,而在构造这个实例的过程中,我们会调用 getSpringFactoriesInstances 方法。

这个方法是实现自动配置过程中非常重要的一个方法,这个方法内部调用了 SpringFactoriesLoader.loadFactoryNames 方法,我们可以看一下 SpringFactoriesLoader 做了什么:

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

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

  private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    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 factoryTypeName = ((String) entry.getKey()).trim();
          for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
            result.add(factoryTypeName, factoryImplementationName.trim());
          }
        }
      }
      cache.put(classLoader, result);
      return result;
    }
    catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
                                         FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
  }
}
复制代码

SpringFactoriesLoader 是 Spring Framework 提供的一个工具类,调用这个类的 loadFactoryNames 方法后,会调用 loadSpringFactories 方法,这个方法内部会通过 ClassLoader.getResources 方法获取所有的 META-INF/spring.factories 文件中的内容1

这是非常关键的一点,自动配置便从这里开始了。

Spring Boot AutoConfigure

Spring Boot 中的自动配置很大一部分是通过 spring-boot-autoconfigure 完成的,SpringApplication 启动时会解析它的 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,\

......
复制代码

可以看到,这个文件中声明了很多的类,也就是说,自动配置就是通过这些类来完成的。我们可以来看一下这些类做了什么:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
  public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

  public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

  @Configuration
  @Conditional(DefaultDispatcherServletCondition.class)
  @ConditionalOnClass(ServletRegistration.class)
  @EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
  protected static class DispatcherServletConfiguration {
    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
      DispatcherServlet dispatcherServlet = new DispatcherServlet();
      return dispatcherServlet;
    }
  }
}
复制代码

上面这个类是经过极大简化的 DispatcherServletAutoConfiguration 类,可以看到,这个类内部声明配置了 DispatcherServlet, 通常情况下,在使用 Spring MVC 的过程中,这是由我们自己完成的,但是在这里,自动配置帮助我们完成了这一步骤。

除了 DispatcherServletAutoConfiguration 以外,还有很多类似的配置类,这些类利用了一些很有用的 Spring 特性来完成整个配置过程。

条件注解

Spring Boot 的自动配置类使用了大量的条件注解,通过这些条件注解,Spring Boot 很好的完成了配置工作,同时,因为存在条件注解的原因,我们可以覆盖 Spring Boot 的默认配置。

条件注解有很多,这篇博客也不是专门介绍这方面的博客,因此这里简单列举三个在自动配置中大量使用的注解:

条件注解作用
@ConditionalOnClass仅在存在某个 Class 的情况下生效
@ConditionalOnBean仅在存在某个 Bean 的情况下生效
@ConditionalOnMissingBean仅在不存在每个 Bean 的情况下生效

其中,通过 @ConditionalOnClass 注解,我们便可以通过引入 不同依赖 的方式应用不同的配置,比如,当类路径下存在 jackson 的情况下,Spring 可以自动完成 jackson 的相关配置。

同时,通过 @ConditionalOnBean 可以保证配置依赖的 Bean 存在时才进行下一步的配置,而 @ConditionalOnMissingBean 正好与之相反,它要求相应的 Bean 不存在才执行下一步的配置。

这意味着,我们可以自己配置某种 Bean,由于 Spring Boot 会先执行我们的配置,因此,当我们配置的 Bean 存在后,相应的默认配置就不会再执行。而且,还不会影响后序的配置过程。

于是,我们便可以通过定义自己的 Bean 的方式覆盖默认的配置。

WebApplicationType

Spring Boot 自动配置中还存在非常重要的一环,那就是 WebApplicationType 的判断,这通过 WebApplicationType.deduceFromClasspath() 完成:

public enum WebApplicationType {
  NONE, SERVLET, REACTIVE;

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

这是该枚举的部分代码,可以看到,它是通过判断某几个类是否存在来判断 WebApplicationType 的。

一开始对于 WebApplicationType 我是不太在意的,后来在使用 Spring Boot 的过程中发现自己的程序启动后瞬间就退出才开始关注这一问题,我发现原因是 tomcat 的依赖被设置为了 provided, 这使得程序直接编译运行后在类路径下找不到相关的类,因此 Spring Boot 就将 WebApplicationType 判断为了 NONE,于是便没有启动 tomcat server.

在解决这个问题的过程中,发现虽然自动配置主要是由 spring-boot-autoconfigure 完成的,但还是有一部分配置在其他的 Spring Boot 组件中。

@SpringBootApplication

这里主要是想介绍一下 @SpringBootApplication 包含的 @ComponentScan 注解。

由于我学习 Spring 是通过《Spring 实战》这本书来学习的,它并没有详细介绍 @ComponentScan 的作用,在尝试后我才发现:

  • @ComponentScan 默认会自动扫描当前包及当前包的所有子包中的 Bean
  • @ComponentScan 如果扫描到 @Configuration,那么就会自动导入相关的配置

这也许就是为什么 Spring Boot 应用程序往往将 Application.java 放在各个子包外面的原因,通过这种方式,就可以自动完成所有子包中的配置和 Bean 的扫描了。

简单总结

到了这里就可以简单总结一下 Spring Boot 的自动配置原理了:

  1. SpringApplication 启动时会检测当前的运行环境,得到 WebApplicationType
  2. SpringApplication 启动时会解析文件 spring-boot-autoconfigure/META-INF/spring.factories 中的内容,得到相关的自动配置实现类
  3. SpringApplication 会先通过组件扫描导入你自定义的配置,这些配置可以覆盖默认配置
  4. SpringApplication 会通过条件注解应用相关自动配置实现类中定义的配置

也就是说,Spring Boot 中的自动配置更像是由 Spring Boot 开发人员预定义好的配置,我们也可以借助相应的方式实现自己的自动配置。

结语

在某种程度上,我是被 自动配置 这个名字给唬住了,在了解其原理后,发现它的原理并没有那么复杂,某种程度上来说,还很朴素 @_@

当然了,自动配置的详细流程肯定比这篇博客中的流程复杂的多,但是,在掌握了基本的原理以后,我们便可以干预这个流程(虽然不掌握也同样可以),从而再次将脱离了自己控制的那一部分代码,再次掌握在自己手里。

Footnotes

1 方法 ClassLoader.getResources 会获取所有 类路径 下的指定的资源文件

转载于:https://juejin.im/post/5cf23e055188252d18205de7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值