一文搞懂面试官常问的: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
来源:稀土掘金
评论