一文搞懂面试官常问的:SpringBoot 自动配置原理
一、SpringBoot 是什么
SpringBoot 的诞生就是为了简化 Spring 中繁琐的 XML 配置,其本质依然还是 Spring 框架,使用 SpringBoot 之后可以不使用任何 XML 配置来启动一个服务,使得我们在使用微服务架构时可以更加快速的建立一个应用。
简单来说就是 SpringBoot 其实不是什么新的框架,它默认配置了很多框架的使用方式。
二、SpringBoot 的特点
- 提供了固定的配置来简化配置,即约定大于配置 
- 尽可能地自动配置 Spring 和第三方库,即能自动装配 
- 内嵌容器,创建独立的 Spring 应用 
- 让测试变的简单,内置了 JUnit、Spring Boot Test 等多种测试框架,方便测试 
- 提供可用于生产的特性,如度量、运行状况检查和外部化配置。 
- 完全不需要生成代码,也不需要 XML 配置。 
三、启动类
下面探究 SpringBoot 的启动原理,关于一些细节就不赘述,我们捉住主线分析即可。
注意:本文的 SpringBoot 版本为 2.6.1
3.1 @SpringBootApplication
一切的来自起源 SpringBoot 的启动类,我们发现 main 方法上面有个注解:@SpringBootApplication
@SpringBootApplication 标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main 方法来启动 SpringBoot 应用;它的本质是一个组合注解,我们点进去查看该类的元信息主要包含 3 个注解:
- @SpringBootConfiguration(里面就是 @Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已)
- @EnableAutoConfiguration(开启自动配置)
- @ComponentScan(包扫描)
注:@Inherited 是一个标识,用来修饰注解,如果一个类用上了 @Inherited 修饰的注解,那么其子类也会继承这个注解。
我们下面逐一分析这 3 个注解作用
3.1.1 @SpringBootConfiguration
我们继续点@SpringBootConfiguration进去查看源码如下:
@Configuration 标注在某个类上,表示这是一个 springboot 的配置类。可以向容器中注入组件。
3.1.2 @ComponentScan
- @ComponentScan:配置用于 Configuration 类的组件扫描指令。 
- 提供与 Spring XML 的 context:component-scan 元素并行的支持。 
- 可以 basePackageClasses 或 basePackages 来定义要扫描的特定包。 如果没有定义特定的包,将从声明该注解的类的包开始扫描。 
3.1.3 @EnableAutoConfiguration
@EnableAutoConfiguration 顾名思义就是:开启自动导入配置
这个注解是 SpringBoot 的重点,我们下面详细讲解
四、@EnableAutoConfiguration
我们点进去看看该注解有什么内容
4.1 @AutoConfigurationPackage
- 自动导入配置包 
- 点进去查看代码: 
@Import 为 spring 的注解,导入一个配置文件,在 springboot 中为给容器导入一个组件,而导入的组件由 AutoConfigurationPackages.class 的内部类Registrar.class 执行逻辑来决定是如何导入的。
4.1.1 @Import({Registrar.class})
点 Registrar.class 进去查看源码如下:
注:Registrar 实现了ImportBeanDefinitionRegistrar类,就可以被注解 @Import 导入到 spring 容器里。
这个地方打断点
 
 运行可以查看到(String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])的值为com.ljw.springbootwork:当前启动类所在的包名
结论:@AutoConfigurationPackage 就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描注冊到 spring 容器中。
4.2@Import({AutoConfigurationImportSelector.class})
作用:AutoConfigurationImportSelector开启自动配置类的导包的选择器,即是带入哪些类,有选择性的导入:
点 AutoConfigurationImportSelector.class 进入查看源码,这个类中有两个方法见名知意:
- selectImports:选择需要导入的组件 
- getAutoConfigurationEntry:根据导入的 @Configuration 类的 AnnotationMetadata 返回 AutoConfigurationImportSelector.AutoConfigurationEntry 
this.getCandidateConfigurations(annotationMetadata, attributes)这里断点查看
 
 configurations 数组长度为 133,并且文件后缀名都为 **AutoConfiguration
结论: 这些都是候选的配置类,经过去重,去除需要的排除的依赖,最终的组件才是这个环境需要的所有组件。有了自动配置,就不需要我们自己手写配置的值了,配置类有默认值的。
我们继续往下看看是如何返回需要配置的组件的
4.2.1 getCandidateConfigurations(annotationMetadata, attributes)
方法如下:
这里有句断言: Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
意思是:“在 META-INF/spring.factories 中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。“
结论: 即是要 loadFactoryNames()方法要找到自动的配置类返回才不会报错。
getSpringFactoriesLoaderFactoryClass()
我们点进去发现:this.getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class这个注解。这个注解和 @SpringBootApplication 下标识注解是同一个注解。
结论:获取一个能加载自动配置类的类,即 SpringBoot 默认自动配置类为 EnableAutoConfiguration
4.2.2 SpringFactoriesLoader
SpringFactoriesLoader 工厂加载机制是 Spring 内部提供的一个约定俗成的加载方式,只需要在模块的 META-INF/spring.factories 文件,这个 Properties 格式的文件中的 key 是接口、注解、或抽象类的全名,value 是以逗号 “ , “ 分隔的实现类,使用 SpringFactoriesLoader 来实现相应的实现类注入 Spirng 容器中。
注:会加载所有jar包下的 classpath 路径下的 META-INF/spring.factories 文件,这样文件不止一个。
loadFactoryNames()
断点查看 factoryTypeName:
 
 先是将 EnableAutoConfiguration.class 传给了 factoryType 
然后String factoryTypeName = factoryType.getName();,所以factoryTypeName 值为 org.springframework.boot.autoconfigure.EnableAutoConfiguration
loadSpringFactories()
接着查看 loadSpringFactories 方法的作用
这里的 FACTORIES_RESOURCE_LOCATION 在上面有定义:META-INF/spring.factories
META-INF/spring.factories 文件在哪里?? 在所有引入的 java 包的当前类路径下的 META-INF/spring.factories 文件都会被读取,如:
 
 断点查看 result 值如下:
 
 该方法作用是加载所有依赖的路径 META-INF/spring.factories 文件,通过 map 结构保存,key 为文件中定义的一些标识工厂类,value 就是能自动配置的一些工厂实现的类,value 用 list 保存并去重。
 
 在回看 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
因为 loadFactoryNames 方法携带过来的第一个参数为 EnableAutoConfiguration.class,所以 factoryType 值也为 EnableAutoConfiguration.class,那么 factoryTypeName 值为 EnableAutoConfiguration。拿到的值就是 META-INF/spring.factories 文件下的 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值
 
 getOrDefault 当 Map 集合中有这个 key 时,就使用这个 key 值,如果没有就使用默认值空数组
结论:
- loadSpringFactories()该方法就是从“META-INF/spring.factories”中加载给定类型的工厂实现的完全限定类名放到 map 中 
- loadFactoryNames()是根据 SpringBoot 的启动生命流程,当需要加载自动配置类时,就会传入 org.springframework.boot.autoconfigure.EnableAutoConfiguration 参数,从 map 中查找 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值,这些值通过反射加到容器中,之后的作用就是用它们来做自动配置,这就是 Springboot 自动配置开始的地方 
- 只有这些自动配置类进入到容器中以后,接下来这个自动配置类才开始进行启动 
- 当需要其他的配置时如监听相关配置:listenter,就传不同的参数,获取相关的 listenter 配置。 
五、流程总结图
 
 六、常用的 Conditional 注解
- 在加载自动配置类的时候,并不是将 spring.factories 的配置全部加载进来,而是通过 @Conditional 等注解的判断进行动态加载 
- @Conditional 其实是 spring 底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效。 
- 常用的 Conditional 注解: - @ConditionalOnClass : classpath 中存在该类时起效 - @ConditionalOnMissingClass : classpath 中不存在该类时起效 - @ConditionalOnBean : DI 容器中存在该类型 Bean 时起效 - @ConditionalOnMissingBean : DI 容器中不存在该类型 Bean 时起效 - @ConditionalOnSingleCandidate : DI 容器中该类型 Bean 只有一个或 @Primary 的只有一个时起效 - @ConditionalOnExpression : SpEL 表达式结果为 true 时 - @ConditionalOnProperty : 参数设置或者值一致时起效 - @ConditionalOnResource : 指定的文件存在时起效 - @ConditionalOnJndi : 指定的 JNDI 存在时起效 - @ConditionalOnJava : 指定的 Java 版本存在时起效 - @ConditionalOnWebApplication : Web 应用环境下起效 - @ConditionalOnNotWebApplication : 非 Web 应用环境下起效 
七、@Import 支持导入的三种方式
- 带有 @Configuration 的配置类 
- ImportSelector 的实现 
- ImportBeanDefinitionRegistrar 的实现 
作者:小伙子 vae
链接:https://juejin.cn/post/7046554366068654094
来源:稀土掘金











 
    
评论