【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的

本文已收录 【修炼内功】跃迁之路

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第1张图片

林中小舍.png

微信关注“林中小舍”,林小二带你聊技术!

上篇文章 Spring Framework中的注解是如何运作的 介绍了Spring Framework中各种注解的运作方式,(在SpringBoot推出之前)对于组件的使用还需要手动进行配置(无论xml/groovy文件方式还是注解方式),如DataSource、SessionFactory、TransactionManager、DispatcherServlet等等

SpringBoot提供了一种新的方式用于简化Spring应用的搭建及开发过程,通过各种starter实现组件的自动装配(autoconfig),将约定优于配置表现的淋漓尽致,在此基础上还提供了众多扩展点用以修改约定的配置及默认的组件功能

那,SpringBoot是如何实现自动装配的?(SpringBoot提供的starter列表见 Using boot starter

快速生成Boot应用

1. SpringBoot的启动

SpringBoot应用一般会将 SpringApplication@SpringBootApplication 放一起配合使用

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
            SpringApplication app = new SpringApplication(Application.class);
        app.setWebApplicationType(WebApplicationType.NONE);
        app.run(args);

        // 或者
        // SpringApplication.run(Application.class, args);

        // 或者
        // new SpringApplicationBuilder(Application.class)
        //        .profiles("dev")
        //        .properties("spring.application.name=spring-boot-demo")
        //        .run(args);
    }
}
这里,不一定非要使用 @SpringBootApplication注解, SpringApplication也不一定非要放在main入口中

在此,将SpringApplication及@SpringBootApplication的作用分两个小结详解

上例中展示了三种SpringApplication的编码方式,但不论何种方式都离不开两个步骤:创建(SpringApplication#new);运行(SpringApplication#run)

1.1 SpringApplication创建

在SpringApplication创建过程中初始化了一些参数,各参数的作用/使用会在下文中得以解释

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // ResourceLoader
    this.resourceLoader = resourceLoader;
    // 主入口类
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // web类型 NONE | SERVLET | REACTIVE
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 设置initializers(org.springframework.context.ApplicationContextInitializer)
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置listeners(org.springframework.context.ApplicationListener)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // main函数所处的类
    this.mainApplicationClass = deduceMainApplicationClass();
}

这里需要着重介绍的是在初始化某些参数时使用的一些逻辑

1.1.1 web类型的探测

web类型的探测用到了WebApplicationType#deduceFromClasspath,其逻辑如下

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第2张图片

或许你也遇到过类似的情况,本身并不是一个web工程,却因为某些原因(如引入第三方包的不规范)导致探测到了REACTIVE/SERVLET从而进入了web启动流程(ReactiveWebServerApplicationContext / ServletWebServerApplicationContext)进而引发其他的问题

1.1.2 spring.factories文件初探

Java中SPI(Service Provider Interface)的概念或许各位都不陌生,其提供了一套用来被第三方实现或者扩展的API,用以启用框架扩展、替换组件,将装配的控制权移到程序之外(如最为典型的 jdbc dirver)

Spring中大量使用了该思想,spring.factories是其一种实现形式,spring.factories核心加载逻辑在SpringFactoriesLoader#loadFactoryNames / SpringFactoriesLoader#loadSpringFactories

loadSpringFactories

以spring-boot中spring.factories片段为例

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

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

SpringFactoriesLoader#loadSpringFactories加载后的Map,key为等号前的接口类,value为等号后的实现类列表

再次回到SpringApplication创建过程中对initializerslisteners的初始化,其分别加载并初始化了spring.factories文件中的org.springframework.context.ApplicationContextInitializerorg.springframework.context.ApplicationListener

Java SPI及Spring Factories的几点对比如下

Java SPI Spring Factories
文件位置 META-INF/services/{interface full class name} META-INF/spring.factories
文件内容 每行一个接口实现类,可以多行 properties文件格式,key为接口类全路径,value为实现类全路径(多个间使用逗号分隔)
加载方法 ServiceLoader#load SpringFactoriesLoader#loadFactoryNames/#loadFactories

Q&A

  • 是否可以自定义META-INF/spring.factories,用以扩展Spring中用到的组件?

    可以,事实上Spring也鼓励这样做

  • 是否可以使用META-INF/spring.factories实现自己的逻辑?

    可以,SpringFactoriesLoader中的方法为公开的,并且SpringFactoriesLoader的实现在spring-core中,其使用并不局限于spring-boot

注:使用SpringFactorieLoader加载并初始化的实例通常并没有被IOC管理,通常也没有实现依赖注入的能力

参考 SpringFactoriesLoader#loadFactories / SpringApplication#createSpringFactoriesInstances

1.1.3 主main函数入口类探测

mainApplicationClass用以记录main函数所在类,其探测实现在SpringApplication#deduceMainApplicationClass,逻辑则略显“奇巧”,通过构造RuntimeException获取其stackTrace,分析每一个StackTraceElement找到调用链上的main函数,并返回该函数所在类

关于mainApplicationClass的作用会在下文介绍

1.2 SpringApplication启动

SpringApplication的启动过程要比创建过程实在的多

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第3张图片

SpringApplicationRunListener配置在spring.factories中,其可以针对SpringBoot启动过程中的各事件进行监听(详见SpringApplicationRunListener中的方法定义)

Spring错综复杂的逻辑关系难以在一张流程图中理清所有,惯例,将其拆分并各个击破

1.2.1 ApplicationArguments

在启动jvm时通常可以添加一些启动参数,诸如-jar-XX:CMSTriggerRatio-Dproperty以及自定义参数等

通常有两种参数可以被Spring使用,-Dproperty=value 及--property=value

前者为jvm的能力,其将具体的参数值设置到系统参数中,以便任意的框架均可通过System#getProperty获取

-D property= value

Sets a system property value. The property variable is a string with no spaces that represents the name of the property. The value variable is a string that represents the value of the property. If value is a string with spaces, then enclose it in quotation marks (for example -Dfoo="foo bar").

https://docs.oracle.com/javas...

后者为spring的能力,其底层使用SimpleCommandLinePropertySource来解析,在SpringBoot应用中还可以通过bean-type或bean-name(“springApplicationArguments”)来注入ApplicationArguments,以便在自己的逻辑中获取--property=value类型的启动参数

1.2.2 构造Environment

org.springframework.boot.SpringApplication#prepareEnvironment

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第4张图片

流程图中ConversionService的作用可以参考org.springframework.beans.TypeConverter的使用(Bean是如何被创建的

同时Spring会将启动参数中的-Dproperty=value 及--property=value一并合并到Environment中

Environment创建过程中会将SystemProperties及SystemEnvironment一并合并到Environment中

见AbstractEnvironment#new及StandardEnvironment#customizePropertySources

注意到,application配置文件中spring.main.*下的配置可以修改SpringApplication中的参数,如

spring.main.log-startup-info=false
spring.main.lazy-initialization=true
spring.main.banner-mode=LOG
# 见SpringApplication中支持的参数设置
# https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features
Binder的使用见 Property Binding in Spring Boot 2.0

1.2.3 输出Banner

org.springframework.boot.SpringApplication#printBanner

通常在Spring启动时会看到诸如下图的banner,其实spring是提供了强大定制能力的,具体使用可以参考Customizing the Banner

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第5张图片

Spring同时支持文本(直接输出文本内容)及图片(将图片解析为文本进行绘制)两种banner能力,其分别通过ResourceBanner及ImageBanner实现,具体见SpringApplicationBannerPrinter#getBanner

1.2.4 构造ApplicationContext

org.springframework.boot.SpringApplication#createApplicationContext

这里并没有太多需要介绍的,spring会根据webApplicationType生成不同的ApplicationContext

webApplicationType ApplicationContext
WebApplicationType.SERVLET AnnotationConfigServletWebServerApplicationContext
WebApplicationType.REACTIVE AnnotationConfigReactiveWebServerApplicationContext
默认 AnnotationConfigApplicationContext

1.2.5 ApplicationContext初始化前

org.springframework.boot.SpringApplication#prepareContext

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第6张图片

这里需要重点介绍几个逻辑

1.2.6 ApplicationContextInitializer#initialize

文章开始初介绍了Spring会从spring.factories中加载ApplicationContextInitializer,在这里则会用到它们的initialize方法,通常可以通过该方法注册一些额外的bean或者BeanFactoryPostProcessor,甚至对ApplicationContext做一些修改

如spring-boot-autoconfiguration中注册的SharedMetadataReaderFactoryContextInitializer

class SharedMetadataReaderFactoryContextInitializer
        implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        applicationContext.addBeanFactoryPostProcessor(new CachingMetadataReaderFactoryPostProcessor());
    }
}

再如spring-boot-devtools中注册的RestartScopeInitializer

public class RestartScopeInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        applicationContext.getBeanFactory().registerScope("restart", new RestartScope());
    }
}

1.2.6.1 lazyInitialization

如果该属性被设置为true,spring则会向ApplicationContext中添加LazyInitializationBeanFactoryPostProcessor,其会将所有bean的lazyInit属性设置为true,以延迟bean的加载并加快SpringBoot的启动速度,其弊端则是将一些隐性的错误延迟到了首次加载阶段(如注入问题)

A downside of lazy initialization is that it can delay the discovery of a problem with the application.

当然也有例外情况,可通过注册LazyInitializationExcludeFilter进行排除实现

1.2.6.2 primarySources及sources的加载

primarySources可通过SpringApplication的构造传入,sources可通过spring.main.sources设置

通常我们需要将@SpringBootApplication注解添加到primarySource或source类上,其目的就是在加载primarySources或sources时一并解析

SpringApplication的启动逻辑(run函数)并不是SpringBoot的全部,其大部分功能由@SpringBootApplication注解代入

至此还没有执行ApplicationContext的refresh动作,也就是说primarySources及sources(如果存在则包括@SpringBootApplication)的解析是优先于ApplicationContext#refresh的

当然,理论上可以将@SpringBootApplication注解放到其他类上,但请保证该类可以被Spring扫描到

1.2.7 ApplicationContext的初始化

org.springframework.boot.SpringApplication#refreshContext

该部分没有十分特别的逻辑,执行ApplicationContext的refresh方法并注册shutdown hook(registerShutdownHook)

ApplicationContext的refresh逻辑参考ApplicationContext给开发者提供了哪些(默认)扩展

1.2.8 ApplicationContext初始化后

org.springframework.boot.SpringApplication#afterRefresh

该部分并没有实际的实现,由子类实现

1.2.9 异常处理

org.springframework.boot.SpringApplication#handleRunFailure

在SpringApplication启动过程中发生异常则有此部分处理,这里需要关注的是,具体的异常可由SpringBootExceptionReporter#reportException接收并处理

而SpringBootExceptionReporter则由spring.factories中的org.springframework.boot.SpringBootExceptionReporter进行注册(通常为org.springframework.boot.diagnostics.FailureAnalyzers)


至此完成了SpringApplication的初始化及执行过程的介绍,但对于众多SpringBoot的能力貌似都没有体现,正如上文所述,SpringBoot大部分功能由@SpringBootApplication注解代入

2. @SpringBootApplication注解的威力

如上文介绍,通常需要将@SpringBootApplication注解添加到primarySource或source类上,其目的是在加载primarySources或sources时一并解析,并且此过程一般在ApplicationContext refresh之前

对于注解的解析逻辑见Spring Framework中的注解是如何运作的

首先看一下@SpringBootApplication注解的层级结构我们对其进行拆分并依次介绍其作用

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第7张图片

2.1. ComponentScan

Spring Framework中的注解是如何运作的一文中有介绍,如果@ComponentScan中没有指定任何扫描packages,Spring则会使用目标class所在的package进行扫描,这也是为什么我们在@SpringBootApplication上没有指定scanBasePackages时,总是会扫描@SpringBootApplication修饰类所在package的原因

此外,@SpringBootApplication中默认为@ComponentScan添加了两个excludeFilter,TypeExcludeFilterAutoConfigurationExcludeFilter

前者的排除规则由TypeExcludeFilter类型的bean实现,可自行实现并注册

后者则为了防止primarySource与spring.factories中org.springframework.boot.autoconfigure.EnableAutoConfiguration同时指定一个类时的重复注册

2.2 SpringBootConfiguration

该注解仅是简单继承了@Configuration,@Configuration介绍详见Spring Framework中的注解是如何运作的

2.3 EnableAutoConfiguration

@EnableAutoConfiguration注解主要用来发现需要装配的配置类,并进行自动装配

该注解使用@Import直接导入了AutoConfigurationImportSelector,同时继承了@AutoConfigurationPackage,而@AutoConfigurationPackage也简单地使用@Import导入了AutoConfigurationPackages.Registrar

这里我们对导入的两个类进行分析(@Import的介绍详见Spring Framework中的注解是如何运作的

2.4 AutoConfigurationImportSelector

AutoConfigurationImportSelector#selectImports

该注册器的逻辑其实非常简单

  • spring.factories中org.springframework.boot.autoconfigure.EnableAutoConfiguration记录了所有需要注册的配置类
  • @EnableAutoConfiguration中的exclude及excludeName参数,及spring.autoconfigure.exclude,配置了不需要注册的配置类信息
  • spring-autoconfigure-metadata.properties中记录了各种配置类的注册条件

根据以上信息,可以筛选出最终需要注册的配置类,并对其进行配置

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第8张图片

spring.factories中org.springframework.boot.autoconfigure.EnableAutoConfiguration,示例如

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

spring-boot-autoconfigure工程中的spring.factories文件中定义了大量的XxxAutoConfiguration,用以在引入不同starter时对相关Bean的自动装配(SpringBoot提供的starter列表见 Using boot starter

spring-boot-autoconfigure工程中的spring.factories文件定义了spring-boot绝大部分starter自动装配的XxxAutoConfiguration,但通常来讲并不是所有的XxxAutoConfiguration都需要装配

决定一个XxxAutoConfiguration是否需要进行装配,由以下几方面控制

  • @EnableAutoConfiguration参数控制
  • spring.autoconfigure.exclude参数控制
  • spring-autoconfigure-metadata.properties文件中指定的规则控制
  • XxxAutoConfiguration本身的条件控制,如硬编码的@ConditionalOn

spring-autoconfigure-metadata.properties文件,示例如

org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,reactor.core.publisher.Flux,org.springframework.data.cassandra.core.ReactiveCassandraTemplate
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration.ConditionalOnWebApplication=SERVLET
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureOrder=-2147483648

该文件定义了各种配置类自动装配的条件,properties文件的格式为${配置类全路径}.${条件类型}=${条件值}

条件类型可以为ConditionalOnClass、AutoConfigureAfter、AutoConfigureOrder等,其逻辑由org.springframework.boot.autoconfigure.AutoConfigurationImportFilter定义解析,AutoConfigurationImportFilter同样在spring.factories中指定

Q: 如何借助spring.factories在SpringBoot启动时自动装配自定义的Bean(s)? 如何使用该特性自定义starter?

Q&A:

SpringBoot是如何通过@AutoConfigureBefore、@AutoConfigureAfter、AutoConfigureOrder控制配置类加载顺序的?

AutoConfigurationImportSelector实现了DeferredImportSelector(DeferredImportSelector的原理参考Spring Framework中的注解是如何运作的),其在ConfigurationClassParser#deferredImportSelectorHandler.process()阶段,使用AutoConfigurationSorter针对以上注解对配置类进行了加载的排序

  • AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports
  • AutoConfigurationImportSelector.AutoConfigurationGroup#sortAutoConfigurations

2.4.1 AutoConfigurationPackages.Registrar

AutoConfigurationPackages.Registrar#registerBeanDefinitions

该注册器的逻辑则略显单薄,其注册了一个名为org.springframework.boot.autoconfigure.AutoConfigurationPackages类型为BasePackages的bean,该bean中记录了被@EnableAutoConfiguration修饰的类所在的package

2.5 ConfigurationPropertiesScan

@ConfigurationPropertiesScan主要用来发现@ConfigurationProperties,并对其进行解析、注册

同样的,该注解使用@Import直接导入了ConfigurationPropertiesScanRegistrar,同时继承了@EnableConfigurationProperties,而@EnableConfigurationProperties也简单地使用@Import导入了@EnableConfigurationPropertiesRegistrar

这里我们对导入的两个类进行分析

2.5.1 ConfigurationPropertiesScanRegistrar

ConfigurationPropertiesScanRegistrar#registerBeanDefinitions

该部分逻辑比较简单,不再附以流程图

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 从@ConfigurationPropertiesScan中获取需要扫描packages
    Set packagesToScan = getPackagesToScan(importingClassMetadata);
    // 在指定packages中扫描被@ConfigurationProperties修饰的类并注册
    scan(registry, packagesToScan);
}

所以整体逻辑

  1. 从@ConfigurationPropertiesScan中(basePackages/basePackageClasses)获取需要扫描的packages,如果均为指定则默认@ConfigurationPropertiesScan修饰类所在package
  2. 在以上package中扫描被@ConfigurationProperties修饰的类并注册,扫描器由ClassPathScanningCandidateComponentProvider创建

以上具体逻辑可阅读源码,扫描逻辑可参考Spring Framework中的注解是如何运作的 - ClassPathBeanDefinitionScanner

2.5.2 EnableConfigurationPropertiesRegistrar

EnableConfigurationPropertiesRegistrar#registerBeanDefinitions

@ConfigurationPropertiesScanRegistrar只是用来在指定packages中发现@ConfigurationProperties并注册,那@ConfigurationProperties的具体解析在哪里?
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // 注册处理器
    registerInfrastructureBeans(registry);
    // 对@EnableConfigurationProperties中指定的类进行注册
    ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
    getTypes(metadata).forEach(beanRegistrar::register);
}

EnableConfigurationPropertiesRegistrar做了两件事

  1. @EnableConfigurationProperties#value可以直接指定被@ConfigurationProperties修饰的类,EnableConfigurationPropertiesRegistrar对其进行注册(注册逻辑可参考Spring Framework中的注解是如何运作的 - AnnotatedBeanDefinitionReader)
  2. 注册一些处理器(以下仅列出其中比较关键的几个处理器)

    • ConfigurationPropertiesBindingPostProcessor
    • ConfigurationPropertiesBinder

2.5.2.1 ConfigurationPropertiesBinder

ConfigurationPropertiesBinder是一个通用能力,底层采用Binder(见Property Binding in Spring Boot 2.0)将@ConfigurationProperties中指定的规则属性绑定到修饰的Bean实例中

2.5.2.2 ConfigurationPropertiesBindingPostProcessor

ConfigurationPropertiesBindingPostProcessor实现了BeanPostProcessor,在其postProcessBeforeInitialization函数中借助ConfigurationPropertiesBinder对@ConfigurationProperties修饰的Bean做属性绑定动作(BeanPostPocessor原理参考Bean是如何被创建的

通常@ConfigurationProperties与@NestedConfigurationProperty配合使用,解决复杂对象属性绑定

参考Nested Properties

starter中使用@ConfigurationProperties绑定属性时,为了快速让使用者了解starter可以进行配置的选项,以及辅助IDE在配置时进行提示、校验、自动补全等,会使用spring-boot-configuration-processor将@ConfigurationProperties所在类的信息映射为元数据文件(META-INF/spring-configuration-metadata.json)

其中描述信息来自标准的javadoc(参考Generating Your Own Metadata by Using the Annotation Processor

2.6 回顾

回顾一下@SpringBootApplication注解的层级结构

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第9张图片

Spring Framework中的注解是如何运作的中有介绍Spring中的注解编程模型,@SpringBootApplication由很多注解组合而成,并且在其中重写了@EnableAutoConfiguration及@ComponentScan的参数,以便在@SpringBootApplication中直接控制后者的参数

是否有曾考虑过,@SpringBootApplication所包含的注解只能以@SpringBootApplication的方式使用么?是否可以单独使用?@SpringBootApplication并没有重写@ConfigurationPropertiesScan及@EnableConfigurationProperties注解参数,那后者的参数如何控制?

凡事不用太过死板,@SpringBootApplication仅仅整合了多个注解的能力,至于组合使用还是单独使用完全由开发者决定,这也是Spring比较友好的地方

对于Bean属性绑定而言,反而比较常用的是@ConfigurationProperties与@EnableConfigurationProperties的组合,而不是@SpringBootApplication,尤其在starter的实现中

@ConfigurationProperties(prefix = "spring.my", ignoreInvalidFields = true)
public class MyProperties { ... }

@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyConfiguration { ... }
Q: 是否还有其他发现?

3. spring.factories浅解

SpringBoot中spring.factories内容非常丰富,这里挑选几个有代表性的进行梳理,更多的内容还需要读者自行探索

3.1 spring-boot

3.1.1 SpringApplicationRunListener

org.springframework.boot.SpringApplicationRunListener (on spring.factories)

上文有介绍,SpringApplicationRunListener可以针对SpringBoot启动过程中的各事件进行监听,其中比较有代表性的EventPublishingRunListener,其在contextLoaded阶段(EventPublishingRunListener#contextLoaded)将spring.factories中配置的ApplicationListener添加到创建的ApplicationContext中

3.1.2 ApplicationListener

org.springframework.context.ApplicationListener (on spring.factories)

ApplicationListener的使用在ApplicationContext给开发者提供了哪些(默认)扩展中有介绍,ApplicationListener的注册也可以在spring.factories中,这里要介绍的一个ApplicationListener是ConfigFileApplicationListener,其在ApplicationEnvironmentPreparedEvent事件(ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent)下加载了spring.factories中所有的EnvironmentPostProcessor,执行其postProcessEnvironment方法对Environment进行增强

3.1.3 EnvironmentPostProcessor

org.springframework.boot.env.EnvironmentPostProcessor (on spring.factories)

从命名上可以看出,EnvironmentPostProcessor是在Environment准备好后对Environment做一些处理

ConfigFileApplicationListener除了实现ApplicationListener之外同时还实现了EnvironmentPostProcessor,在其postProcessEnvironment方法中加载了spring.factories中所有的PropertySourceLoader,将classpath:/,classpath:/config/,file:./,file:./config/下的 application.* 类似的文件加载到Environment中

3.1.4 PropertySourceLoader

org.springframework.boot.env.PropertySourceLoader

PropertySourceLoader则是用来加载配置文件的具体实现,SpringBoot spring.factories中提供了两种实现 PropertiesPropertySourceLoader及YamlPropertySourceLoader,前者用来加载propertiesxml文件,后者用来加载yml文件

从这里可以看出,通过 (on spring.factories)

  1. EventPublishingRunListener(SpringApplicationRunListener)->
  2. ConfigFileApplicationListener(ApplicationListener)->
  3. ConfigFileApplicationListener(EnvironmentPostProcessor)->
  4. Properties/YamlPropertySourceLoader(PropertySourceLoader)

才实现了application.properties / application.yml配置文件的加载

spring.factories所提供服务的体系比较庞大,其在SpringBoot自动装配中起到了至关重要的作用

Q: 如何实现自己的PropertySourceLoader,实现自定义application.*文件的加载

Q: 了解SpringBoot提供的spring.factories内的功能,并对其进行扩展

3.2 spring-autoconfigure

3.2.1 EnableAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration

用来定义需要导入的配置类,SpringBoot starter使用该方式实现配置类的自动导入

3.1.2 AutoConfigurationImportFilter

org.springframework.boot.autoconfigure.AutoConfigurationImportFilter

用来实现类似@Conditional的功能,判断目标配置类是否满足导入条件

SpringBoot提供了OnBeanCondition、OnClassCondition、OnWebApplicationCondition三种Condition,分别处理

  • OnBeanCondition

    @ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnSingleCandidate

  • OnClassCondition

    @ConditionalOnClass、@ConditionalOnMissingClass

  • OnWebApplicationCondition

    @ConditionalOnWebApplication、@ConditionalOnNotWebApplication

等注解的处理

4. 后记

SpringBoot的内容远不止这些,SpringBoot所能实现的功能也远超我目前对它的认识,Spring很强大,但也有它自身的缺点,当下不乏一些优秀的竞争对手

语言与框架只是你迈上下一级台阶的工具和手段,匠人精神不能少,但也要正确看待当今快速变化的世界


【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第10张图片

【修炼内功】[spring-boot] [1] SpringBoot是如何实现自动装配的_第11张图片

你可能感兴趣的:(springboot,源码分析,spring)