spring.factories

getCandidateConfigurations()
->S_pringFactoriesLoader._loadFactoryNames()
-> loadSpringFactories()
这里的方法扫描整个项目(包括依赖包相同路径中的)META-INF/spring.factories文件,这个文件中包含了自动配置需要的一些配置类,包括springboot-boot自己的配置类,以及用户添加的starter类中的META-INF/spring.factories的配置类,这样配置了需要自动配置的所有Configure类
image.png

image.png

spring-autoconfigure-metadata.properties

AutoConfigurationImportSelector类中的 selectImports() 中的AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader) 这一步会读取整个项目中的META-INF/spring-autoconfigure-metadata.properties
因为如果Springboot单单全部加载spring.factories里的内容,那会在启动的时候会报很多类找达不到的错误,因为spring.factories是一个自动装配配置的全集,在我们没有加入某些jar包的情况下肯定会报相关类找不到的错误!spring-autoconfigure-metadata会根据环境和配置剔除掉一部分spring.factories里的组件,所以在加载的时候不会报错
image.png

  1. 上面selectImports()方法中:
  2. configurations = filter(configurations, autoConfigurationMetadata);
  3. - filter(List<String> configurations,AutoConfigurationMetadata autoConfigurationMetadata)
  4. - match(String[] autoConfigurationClasses,AutoConfigurationMetadata autoConfigurationMetadata)
  5. - getOutcomes(String[] autoConfigurationClasses,AutoConfigurationMetadata autoConfigurationMetadata)
  6. - getOutcomes(String[] autoConfigurationClasses,int start, int end, AutoConfigurationMetadata autoConfigurationMetadata)
  7. private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
  8. int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
  9. ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
  10. for (int i = start; i < end; i++) {
  11. String autoConfigurationClass = autoConfigurationClasses[i];
  12. //这里会检查spring-autoconfigure-metadata配置的xx.ConditionalOnClass, 然后使用ClassLoader根据
  13. //ConditionalOnClass上配置的类进行尝试加载,如果都能加载到就可以自动配置ConditionalOnClass标记的配置类
  14. //这里的文件import了不存再的包,但是没有在缺失对应的依赖类情况下加载整个配置类,所以不会报错
  15. Set<String> candidates = autoConfigurationMetadata
  16. .getSet(autoConfigurationClass, "ConditionalOnClass");
  17. if (candidates != null) {
  18. outcomes[i - start] = getOutcome(candidates);
  19. }
  20. }
  21. return outcomes;
  22. }

本文包含:SpringBoot的自动配置原理及如何自定义SpringBootStar等

我们知道,在使用SpringBoot的时候,我们只需要如下方式即可直接启动一个Web程序:

  1. @SpringBootApplication
  2. public class DemoApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(DemoApplication.class, args);
  5. }
  6. }

和我们之前使用普通Spring时繁琐的配置相比简直不要太方便,那么你知道SpringBoot实现这些的原理么

首先我们看到类上方包含了一个@SpringBootApplication注解

  1. @SpringBootConfiguration
  2. @EnableAutoConfiguration
  3. @ComponentScan(
  4. excludeFilters = {@Filter(
  5. type = FilterType.CUSTOM,
  6. classes = {TypeExcludeFilter.class}
  7. ), @Filter(
  8. type = FilterType.CUSTOM,
  9. classes = {AutoConfigurationExcludeFilter.class}
  10. )}
  11. )
  12. public @interface SpringBootApplication {
  13. @AliasFor(
  14. annotation = EnableAutoConfiguration.class
  15. )
  16. Class<?>[] exclude() default {};
  17. @AliasFor(
  18. annotation = EnableAutoConfiguration.class
  19. )
  20. String[] excludeName() default {};
  21. @AliasFor(
  22. annotation = ComponentScan.class,
  23. attribute = "basePackages"
  24. )
  25. String[] scanBasePackages() default {};
  26. @AliasFor(
  27. annotation = ComponentScan.class,
  28. attribute = "basePackageClasses"
  29. )
  30. Class<?>[] scanBasePackageClasses() default {};
  31. }

这个注解上边包含的东西还是比较多的,咱们先看一下两个简单的热热身

@ComponentScan 注解

  1. @ComponentScan(excludeFilters = {
  2. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  3. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

这个注解咱们都是比较熟悉的,无非就是自动扫描并加载符合条件的Bean到容器中,这个注解会默认扫描声明类所在的包开始扫描,例如:
cn.shiyujun.Demo类上标注了@ComponentScan 注解,则cn.shiyujun.controllercn.shiyujun.service等等包下的类都可以被扫描到

这个注解一共包含以下几个属性:

  1. basePackages:指定多个包名进行扫描
  2. basePackageClasses:对指定的类和接口所属的包进行扫
  3. excludeFilters:指定不扫描的过滤器
  4. includeFilters:指定扫描的过滤器
  5. lazyInit:是否对注册扫描的bean设置为懒加载
  6. nameGenerator:为扫描到的bean自动命名
  7. resourcePattern:控制可用于扫描的类文件
  8. scopedProxy:指定代理是否应该被扫描
  9. scopeResolver:指定扫描bean的范围
  10. useDefaultFilters:是否开启对@Component@Repository@Service@Controller的类进行检测

@SpringBootConfiguration注解

这个注解更简单了,它只是对Configuration注解的一个封装而已

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Configuration
  5. public @interface SpringBootConfiguration {
  6. }

@EnableAutoConfiguration注解

这个注解可是重头戏了,SpringBoot号称的约定大于配置,也就是本文的重点自动装配的原理就在这里了

  1. @Import({AutoConfigurationImportSelector.class})
  2. public @interface EnableAutoConfiguration {
  3. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  4. Class<?>[] exclude() default {};
  5. String[] excludeName() default {};
  6. }

简单概括一下,这个注解存在的意义就是:利用@Import注解,将所有符合自动装配条件的bean注入到IOC容器中,关于@Import注解原理这里就不再阐述,见笔记Spring @Import注解源码解析

进入类AutoConfigurationImportSelector,观察其selectImports方法,这个方法执行完毕后,Spring会把这个方法返回的类的全限定名数组里的所有的类都注入到IOC容器中

  1. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  2. if (!this.isEnabled(annotationMetadata)) {
  3. return NO_IMPORTS;
  4. } else {
  5. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
  6. AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
  7. List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
  8. configurations = this.removeDuplicates(configurations);
  9. Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
  10. this.checkExcludedClasses(configurations, exclusions);
  11. configurations.removeAll(exclusions);
  12. configurations = this.filter(configurations, autoConfigurationMetadata);
  13. this.fireAutoConfigurationImportEvents(configurations, exclusions);
  14. return StringUtils.toStringArray(configurations);
  15. }
  16. }

观察上方代码:

  1. 第一行if时会首先判断当前系统是否禁用了自动装配的功能,判断的代码如下:
  1. protected boolean isEnabled(AnnotationMetadata metadata) {
  2. return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
  3. }
  1. 如果当前系统禁用了自动装配的功能则会返回如下这个空的数组,后续也就无法注入bean了
  1. private static final String[] NO_IMPORTS = new String[0];
  1. 此时如果没有禁用自动装配则进入else分枝,第一步操作首先会去加载所有Spring预先定义的配置条件信息,这些配置信息在org.springframework.boot.autoconfigure包下和其他依赖中的META-INF/spring-autoconfigure-metadata.properties文件
  2. 这些配置条件主要含义大致是这样的:如果你要自动装配某个类的话,你觉得先存在哪些类或者哪些配置文件等等条件,这些条件的判断主要是利用了@ConditionalXXX注解,关于@ConditionalXXX系列注解可以参考这篇文章:见笔记SpringBoot条件注解@Conditional
  3. 这个文件里的内容格式是这样的:
  1. org.springframework.boot.actuate.autoconfigure.web.servlet.WebMvcEndpointChildContextConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet
  2. org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,io.micrometer.core.instrument.MeterRegistry
  3. org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
  1. 具体的加载代码就不列出了,无法就是个读取配置文件
  2. 这里放个加载之后的结果图:

SpringBoot自动装配原理解析 - 图4

  1. 获取@EnableAutoConfiguration注解上的exclude、excludeName属性,这两个属性的作用都是排除一些类的
  2. 这里又是关键的一步,可以看到刚才图片中spring-autoconfigure-metadata.properties文件的上方存在一个文件spring.factories,这个文件也不止存在于org.springframework.boot.autoconfigure包里了,所有的包里都有可能存在这个文件,所以这一步是加载整个项目所有的spring.factories文件。这个文件的格式是这样的
  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthIndicatorAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration

Springboot的stater的原理

这里存在一个知识点,SpringBoot中的starter就是依靠这个文件完成的,假如我们需要自定义一个SpringBoot的Star,就可以在我们的项目的META-INF文件夹下新建一个spring.factories文件(也可以配合spring-autoconfigure-metadata.properties设置ConditionalOnClass设置加载条件)

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.shiyujun.TestAutoConfiguration

这样当别的项目依赖我们的项目时就会自动把我们的TestAutoConfiguration类注入到Spring容器中

  1. 删除重复的自动配置类
  2. 下面三行就是去除我们指定排除的配置类
  3. 接着这一行的逻辑稍微复杂一些,主要就是根据加载的配置条件信息来判断各个配置类上的@ConditionalXXX系列注解是否满足需求
  4. 最后就是发布自动装配完成事件,然后返回所有能够自动装配的类的全限定名

到了这里我们已经把SpringBoot自动装配的原理搞清楚了,但是总感觉差点什么,那我们从这些自动装配的类里面挑一个我们比较熟悉的关于Servlet的类来看看咋回事吧://找不到这个类,汗

  1. @Configuration
  2. @ConditionalOnWebApplication(
  3. type = Type.SERVLET
  4. )
  5. public class ServletEndpointManagementContextConfiguration {
  6. public ServletEndpointManagementContextConfiguration() {
  7. }
  8. @Bean
  9. public ExposeExcludePropertyEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(WebEndpointProperties properties) {
  10. Exposure exposure = properties.getExposure();
  11. return new ExposeExcludePropertyEndpointFilter(ExposableServletEndpoint.class, exposure.getInclude(), exposure.getExclude(), new String[0]);
  12. }
  13. @Configuration
  14. @ConditionalOnClass({ResourceConfig.class})
  15. @ConditionalOnMissingClass({"org.springframework.web.servlet.DispatcherServlet"})
  16. public class JerseyServletEndpointManagementContextConfiguration {
  17. public JerseyServletEndpointManagementContextConfiguration() {
  18. }
  19. @Bean
  20. public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
  21. return new ServletEndpointRegistrar(properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
  22. }
  23. }
  24. @Configuration
  25. @ConditionalOnClass({DispatcherServlet.class})
  26. public class WebMvcServletEndpointManagementContextConfiguration {
  27. private final ApplicationContext context;
  28. public WebMvcServletEndpointManagementContextConfiguration(ApplicationContext context) {
  29. this.context = context;
  30. }
  31. @Bean
  32. public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
  33. DispatcherServletPathProvider servletPathProvider = (DispatcherServletPathProvider)this.context.getBean(DispatcherServletPathProvider.class);
  34. String servletPath = servletPathProvider.getServletPath();
  35. if (servletPath.equals("/")) {
  36. servletPath = "";
  37. }
  38. return new ServletEndpointRegistrar(servletPath + properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
  39. }
  40. }
  41. }

自上而下观察整个类的代码,你会发现这些自动装配的套路都是一样的:

  1. 如果当前是Servlet环境则装配这个bean
  2. 当存在类ResourceConfig以及不存在类DispatcherServlet时装配JerseyServletEndpointManagementContextConfiguration
  3. 当存在DispatcherServlet类时装配WebMvcServletEndpointManagementContextConfiguration