博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Boot 自动配置原理
阅读量:7128 次
发布时间:2019-06-28

本文共 10850 字,大约阅读时间需要 36 分钟。

Table of Contents

前言

在学习了 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
Collection
getSpringFactoriesInstances(Class
type) { return getSpringFactoriesInstances(type, new Class
[] {}); } private
Collection
getSpringFactoriesInstances(Class
type, Class
[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set
names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List
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
loadFactoryNames(Class
factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map
> loadSpringFactories(@Nullable ClassLoader classLoader) { try { Enumeration
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 文件中的内容

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

Spring Boot AutoConfigure

Spring Boot 中的自动配置很大一部分是通过 spring-boot-autoconfigure 完成的,SpringApplication 启动时会解析它的 META-INF/spring.factories 文件:

# Initializersorg.springframework.context.ApplicationContextInitializer=\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener# Application Listenersorg.springframework.context.ApplicationListener=\org.springframework.boot.autoconfigure.BackgroundPreinitializer# Auto Configuration Import Listenersorg.springframework.boot.autoconfigure.AutoConfigurationImportListener=\org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener# Auto Configuration Import Filtersorg.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\org.springframework.boot.autoconfigure.condition.OnBeanCondition,\org.springframework.boot.autoconfigure.condition.OnClassCondition,\org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition# Auto Configureorg.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

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

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

你可能感兴趣的文章
Android Studio第六期 - 横向GridView
查看>>
Office Communication Server(OCS) 2007的部署
查看>>
WPF中打印问题的探讨
查看>>
QOS中DSCP/COS/IP的映射关系
查看>>
在VMWare Workstation 9 实现嵌套虚拟化设置【虚拟机再虚拟化】
查看>>
VR+旅遊(或酒店)
查看>>
大网高级技术笔记(二)
查看>>
Zabbix 4.0.0 新功能介绍
查看>>
简单的Shell脚本
查看>>
简单shell脚本监控网站页面
查看>>
zookeeper 安装使用教程
查看>>
asp.net core系列
查看>>
redis备份实操
查看>>
浅谈Oracle SQL trace
查看>>
MySQL基础备忘(1)
查看>>
技术公开课:SQL Server 高可用性解决方案概述(下)
查看>>
Skype for Business Server 2015-04-前端服务器-5-创建DNS记录
查看>>
Lync Server外部访问系列PART4:部署反向代理
查看>>
DPM2012系列之六:在Win7上安装DPM远程管理控制台
查看>>
PowerShell 学习笔记——运行命令
查看>>