今天,我们来详细的聊一聊SpringBoot自动配置原理,学了这么久,你学废了吗? |
您所在的位置:网站首页 › springboot为什么能自动配置 › 今天,我们来详细的聊一聊SpringBoot自动配置原理,学了这么久,你学废了吗? |
SpringBoot是我们经常使用的框架,那么你能不能针对SpringBoot实现自动配置做一个详细的介绍。如果可以的话,能不能画一下实现自动配置的流程图。牵扯到哪些关键类,以及哪些关键点。 下面我们一起来看看吧!! 前言:阅读完本文: 你能知道 SpringBoot 启动时的自动配置的原理知识你能知道 SpringBoot 启动时的自动配置的流程以及对于 SpringBoot 一些常用注解的了解一步一步 debug 从浅到深。 注意:本文的 SpringBoot 版本为 2.5.2 一、启动类前言什么的,就不说了,大家都会用的,我们直接从 SpringBoot 启动类说起。 代码语言:javascript复制@SpringBootApplication public class Hello { public static void main(String[] args) { SpringApplication.run(Hello.class); } }@SpringBootApplication 标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的main方法来启动 SpringBoot 应用;是我们研究的重点!!!它的本质是一个组合注解,我们点进去,看看javadoc上是怎么写的,分析从浅到深,从粗略到详细。 我们点进去看: 代码语言:javascript复制@Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {}Javadoc上是这么写的 表示声明一个或多个@Bean方法并触发 auto-configuration 和 component scanning 的 configuration 类。 这是一个方便的注解,相当于声明了 @Configuration 、 @EnableAutoConfiguration 和@ComponentScan 。 —为什么它能集成这么多的注解的功能呢? 是在于它上面的 @Inherited 注解, @Inherited 表示自动继承注解类型。 这里的最重要的两个注解是 @SpringBootConfiguration 和 @EnableAutoConfiguration。 1.1、@SpringBootConfiguration我们先点进去看看 @SpringBootConfiguration注解: 代码语言:javascript复制@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration @Indexed public @interface SpringBootConfiguration {}。1.2、@EnableAutoConfiguration再看看 @EnableAutoConfiguration. 代码语言:javascript复制@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration {}1.3、@ComponentScan@ComponentScan:配置用于 Configuration 类的组件扫描指令。 提供与 Spring XML 的 元素并行的支持。 可以 basePackageClasses 或basePackages( 或其别名value )来定义要扫描的特定包。 如果没有定义特定的包,将从声明该注解的类的包开始扫描。 作为了解,不是本文重点。 1.4、探究方向image-20210927135108478主要探究图中位于中间部分那条主线,其他只会稍做讲解。 二、@SpringBootConfiguration我们刚刚已经简单看了一下 @SpringBootConfiguration 啦。 代码语言:javascript复制@Configuration @Indexed public @interface SpringBootConfiguration {}它是 springboot 的配置类,标注在某个类上,表示这是一个 springboot的配置类。 我们在这看到 @Configuration ,这个注解我们在 Spring 中就已经看到过了,它的意思就是将一个类标注为 Spring 的配置类,相当于之前 Spring 中的 xml 文件,可以向容器中注入组件。 不是探究重点。 三、@EnableAutoConfiguration我们来看看这玩意,它的字面意思就是:自动导入配置。 代码语言:javascript复制@Inherited @AutoConfigurationPackage 自动导包 @Import(AutoConfigurationImportSelector.class) 自动配置导入选择 public @interface EnableAutoConfiguration {}从这里顾名思义就能猜到这里肯定是跟自动配置有关系的。 我们接着来看看这上面的两个注解 @AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class) ,这两个才是我们研究的重点。 3.1、@AutoConfigurationPackage点进去一看: 代码语言:javascript复制@Inherited @Import(AutoConfigurationPackages.Registrar.class) public @interface AutoConfigurationPackage {}@Import 为 spring 的注解,导入一个配置文件,在 springboot 中为给容器导入一个组件,而导入的组件由 AutoConfigurationPackages.Registrar.class 执行逻辑来决定的。 往下👇看:Registrar 代码语言:javascript复制static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); } @Override public Set determineImports(AnnotationMetadata metadata) { return Collections.singleton(new PackageImports(metadata)); } }在这个地方我们可以打个断点,看看 new PackageImports(metadata).getPackageNames().toArray(new String[0]) 它是一个什么值。 image-20210927143028116我们用 Evaluate 计算 new PackageImports(metadata).getPackageNames().toArray(new String[0]) 出来可以看到就是 com.crush.hello ,当前启动类所在的包。 继续往下看的话就是和 Spring 注册相关了,更深入 xdm 可以继续 debug。 在这里我们可以得到一个小小的结论: @AutoConfigurationPackage 这个注解本身的含义就是将主配置类(@SpringBootApplication 标注的类)所在的包下面所有的组件都扫描到 spring 容器中。 如果将一个 Controller 放到 com.crush.hello 以外就不会被扫描到了,就会报错。 3.2、@Import(AutoConfigurationImportSelector.class)AutoConfigurationImportSelector 开启自动配置类的导包的选择器(导入哪些组件的选择器) 我们点进 AutoConfigurationImportSelector 类来看看,有哪些重点知识,这个类中存在方法可以帮我们获取所有的配置 代码语言:javascript复制public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { /**选择需要导入的组件 ,*/ @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } //根据导入的@Configuration类的AnnotationMetadata返回AutoConfigurationImportSelector.AutoConfigurationEntry 。 protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); // 可以在这打个断点,看看 返回的数据 List configurations = getCandidateConfigurations(annotationMetadata, attributes); //删除重复项 configurations = removeDuplicates(configurations); // 排除依赖 Set exclusions = getExclusions(annotationMetadata, attributes); //检查 checkExcludedClasses(configurations, exclusions); //删除需要排除的依赖 configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } }我们看看这个断点,configurations 数组长度为131,并且文件后缀名都为 **AutoConfiguration image-20210927153110296这里的意思是将所有需要导入的组件以全类名的方式返回,并添加到容器中,最终会给容器中导入非常多的自动配置类(xxxAutoConfiguration),给容器中导入这个场景需要的所有组件,并配置好这些组件。有了自动配置,就不需要我们自己手写了。 3.2.1、getCandidateConfigurations()我们还需要思考一下,这些配置都从 getCandidateConfigurations 方法中获取,这个方法可以用来获取所有候选的配置,那么这些候选的配置又是从哪来的呢? 一步一步点进去: 代码语言:javascript复制protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 这里有个 loadFactoryNames 方法 执行的时候还传了两个参数,一个是BeanClassLoader ,另一个是 getSpringFactoriesLoaderFactoryClass() 我们一起看看 List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); 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."); return configurations; }看一下getSpringFactoriesLoaderFactoryClass()方法,这里传过去的是 代码语言:javascript复制protected Class getSpringFactoriesLoaderFactoryClass() { return EnableAutoConfiguration.class; }这个 EnableAutoConfiguration 是不是特别眼熟,(我们探究的起点 @EnableAutoConfiguration ,有没有感觉自己离答案越来越近啦) 我们再看看 loadFactoryNames() 方法带着它去做了什么处理: image-20210927155811449先是将 EnableAutoConfiguration.class 传给了 factoryType ,然后 .getName( ) ,所以factoryTypeName 值为 EnableAutoConfiguration。 3.2.2、loadSpringFactories()接下里又开始调用 loadSpringFactories 方法 image-20210927160059271这里的 FACTORIES_RESOURCE_LOCATION 在上面有定义: public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 我们再回到 getCandidateConfigurations 方法处。 image-20210927160251498代码语言:javascript复制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 中没有找到自动配置类。如果您使用自定义包装,请确保该文件是正确的。“ 这个 META-INF/spring.factories 在哪里呢? image-20210927160422635里面的内容: image-20210927160459778我们日常用到的,基本上都有一个配置类。 比如 webmvc, image-20210928112842076我们点进 WebMvcProperties 类中去看一下: image-20210928113056250那这里到底是要干什么呢? image-20210927161053520这里的意思首先是把这个文件的 urls 拿到之后并把这些 urls 每一个遍历,最终把这些文件整成一个properties 对象,loadProperties方法 image-20210927161313114然后再从 properties 对象里边获取一些我们需要的值,把这些获取到的值来加载我们最终要返回的这个结果,结果 result 为 map 集合,然后返回到loadFactoryNames方法中。 然后我们再回到 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); 的调用处。 image-20210927161649177这个 factoryTypeName 值为 EnableAutoConfiguration 因为 loadFactoryNames 方法携带过来的第一个参数为 EnableAutoConfiguration.class,所以 factoryType 值也为 EnableAutoConfiguration.class,那么 factoryTypeName 值为 EnableAutoConfiguration。 image-20210927161846907那么map集合中 getOrDefault 方法为什么意思呢?意思就是当 Map 集合中有这个 key 时,就使用这个 key值,如果没有就使用默认值 defaultValue (第二个参数),所以是判断是否包含 EnableAutoConfiguration 看下图,这不就是嘛? image-20210927161931299所以就是把 spring-boot-autoconfigure-2.5.2.jar/META-INF/spring.factories 这个文件下的EnableAutoConfiguration 下面所有的组件,每一个 xxxAutoConfiguration 类都是容器中的一个组件,都加入到容器中。加入到容器中之后的作用就是用它们来做自动配置,这就是Springboot自动配置开始的地方。 只有这些自动配置类进入到容器中以后,接下来这个自动配置类才开始进行启动 那 spring.factories 中存在那么多的配置,每次启动时都是把它们全部加载吗? 是全部加载嘛?不可能的哈,这谁都知道哈,全部加载启动一个项目不知道要多久去了。它是有选择的。 我们随便点开一个类,都有这个 @ConditionalOnXXX 注解 image-20210927162055644@Conditional 其实是 spring 底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么整个配置类里边的配置才会生效。 所以在加载自动配置类的时候,并不是将 spring.factories 的配置全部加载进来,而是通过这个注解的判断,如果注解中的类都存在,才会进行加载。 这就是SpringBoot的自动配置啦. 四、小结简单总结起来就是: 启动类中有一个 @SpringBootApplication 注解,包含了 @SpringBootConfiguration、 @EnableAutoConfiguration , @EnableAutoConfiguration 代表开启自动装配,注解会去 spring-boot-autoconfigure 工程下寻找 META-INF/spring.factories 文件,此文件中列举了所有能够自动装配类的清单,然后自动读取里面的自动装配配置类清单。因为有 @ConditionalOn 条件注解,满足一定条件配置才会生效,否则不生效。 如: @ConditionalOnClass(某类.class) 工程中必须包含一些相关的类时,配置才会生效。所以说当我们的依赖中引入了一些对应的类之后,满足了自动装配的条件后,自动装配才会被触发。 image-20210927163514187五、自言自语纸上得来终觉浅,绝知此事要躬行。 如果可以,可以自己 debug 一遍,画一画流程图。🛌 (躺平) 你好,我是博主宁在春:主页 希望本篇文章能让你感到有所收获!!! 祝 我们:待别日相见时,都已有所成。 如有疑惑,大家可以留言评论。 如有不足之处,请大家指出来,非常感谢 👨💻。 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |