写点什么

SpringBoot 自动配置原理及手动实现自动配置,35 岁程序员半月 4 轮面试

用户头像
极客good
关注
发布于: 刚刚

可以看到?@Import注解给我们导入了一个?AutoConfigurationImportSelector类,这个类从名字上看起来就和自动配置选择有关系,我们继续点进去看看,发现了一个很重要的方法--selectImports ,如下:



可以看到其中有一个?getCandidateConfigurations的方法,看名称我们可以猜到此方法负责获取所有的自动配置的信息,而此方法的代码如图:



可以看到 SpringBoot 调用了?SpringFactoriesLoader.loadFactoryNames方法获取其中的所有的名称列表,而此方法中我们可以看到一个查找路径的常量:


<pre style="margin: 0px; padding: 8px 0px 6px; max-width: 100%; box-sizing: border-box; word-wrap: break-word !important; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: 0.544px; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background: rgb(27, 25, 24); border-radius: 0px; overflow-y: auto; color: rgb(80, 97, 109); text-align: start; font-size: 10px; line-height: 12px; font-family: consolas, menlo, courier, monospace, "Microsoft Yahei" !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">


  1. publicstaticfinalString FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


</pre>


SpringBoot 会查找当前工程包括所有依赖的 jar 的?META-INF路径下的


spring.factories文件,似乎所有的自动配置的类都是从这里读取来的,而加载的过程代码如下:


<pre style="margin: 0px; padding: 8px 0px 6px; max-width: 100%; box-sizing: border-box; word-wrap: break-word !important; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: 0.544px; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background: rgb(27, 25, 24); border-radius: 0px; overflow-y: auto; color: rgb(80, 97, 109); text-align: start; font-size: 10px; line-height: 12px; font-family: consolas, menlo, courier, monospace, "Microsoft Yahei" !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">


  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { // 若缓存里有直接返回缓存的值

  2. MultiValueMap<String, String> result = cache.get(classLoader);

  3. if (result != null) {

  4. return result;

  5. }

  6. try {

  7. // 类加载器对象存在则用这个加载器获取上面说的常量路径里的资源,不存在则用系统类加载器去获取

  8. Enumeration<URL> urls = (classLoader != null ?

  9. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) ://当前classloader是appclassloader,getResources能获取所有依赖jar里面的META-INF/spring.factories的完整路径

  10. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

  11. result = new LinkedMultiValueMap<>();

  12. while (urls.hasMoreElements()) { // 遍历上述返回的url集合

  13. URL url = urls.nextElement(); // URL类可以获取来自流,web,甚至jar包里面的资源

  14. UrlResource resource = new UrlResource(url);

  15. Properties properties = PropertiesLoaderUtils.loadProperties(resource);

  16. for (Map.Entry<?, ?> entry : properties.entrySet()) { // 解析spring.factories文件

  17. List<String> factoryClassNames = Arrays.asList(

  18. StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));

  19. // spring.facories中配置的不仅仅有自动配置相关的内容,还有其他比如ApplicationContextInitializer等等各种springboot启动的时候,初始化spring环境需要的配置,自动配置只是其中一项。这个cache也是在springboot启动阶段赋值的

  20. result.addAll((String) entry.getKey(), factoryClassNames);

  21. }

  22. }

  23. cache.put(classLoader, result);

  24. return result;

  25. }

  26. catch (IOException ex) {

  27. throw new IllegalArgumentException("Unable to load factories from location [" +

  28. FACTORIES_RESOURCE_LOCATION + "]", ex);

  29. }

  30. }


</pre>


看到这里我们进入当前项目的 maven 本地仓库,找到我们之前的?spring-boot-starter-jdbcjar,解压以后的确有一个 META-INF 目录,但是目录里并没有 spring.factories 文件,只有一个 spring.provides 文件,当我们打开此文件后以后,发现里面并没有我们想象的自动配置相关的类信息,如图:



这里的 provides 提供者是什么意思?难道是后面的三个名称是 jar 的名称?spring-boot-starter-jdbc.jar 会帮我们自动的下载依赖这三个 jar,所以我们想要的 spring.factories 文件就在这几个 jar 里面?


当我们查看仓库的时候,的确发现了这几个 jar,但是很遗憾,这几个 jar 仅仅就是基础 jar,并不包含这些文件,那么,spring.factories 文件在哪?


还记得我们给 SpringBoot 启动类上标记了 exclude = DataSourceAutoConfiguration.class ,就可以排除了 jdbc 数据源自动配置带来的问题了,那我们去看看这里有什么线索吧,点开?DataSourceAutoConfiguration类,我们可以看到有一个?@Conditional族的组合条件注解,限制为当 DataSource.class,,EmbeddedDatabaseType.class 被 classloader 加载,则这个配置类生效 ,那么当我们加入?spring-boot-starter-jdbc以后,会帮我们自动依赖相关的三个 jar,这个时候,这些 class 也会被加载进去,此条件就满足了,自然就触发了自动配置,那么,这个自动配置自然由 SpringBoot 提供的了,我们查看 SpringBoot 的 star,终于找到了 META-INF/spring.factories 文件,打开内容如下:



至此我们的猜想已经得到验证,SpringBoot 的自动配置是依靠读取 META-INF/spring.factories 文件的内容进行配置,并且根据**@Conditional**族的条件注解,进行限制是否自动配置,从而实现动态的配置与排除 bean 的功能。

动手实现自己的自动配置

了解了自动配置的原理,我们不禁有了想自己实现一个自动配置的想法,首先我们来整理一下自动配置所需要的步骤:


1.编写 Java Config 类,使用 @Configuration 注解来进行 bean 自动配置的条件选择


2.编写 META-INF/spring.factories 文件,将我们需要自动配置的类编写进去,使得 SpringBoot 在读取的时候能将此类加载进去


首先我们创建一个自动配置的 Maven 工程,名称为--?gank-spring-boot-autoconfigure,在此工程的 pom 中我们只要依赖 Springboot 的自动配置相关 jar 以及我们需要被自动配置进去的类所在的 jar,pom 依赖如下:


<pre style="margin: 0px; padding: 8px 0px 6px; max-width: 100%; box-sizing: border-box; word-wrap: break-word !important; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: 0.544px; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background: rgb(27, 25, 24); border-radius: 0px; overflow-y: auto; color: rgb(80, 97, 109); text-align: start; font-size: 10px; line-height: 12px; font-family: cons


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


olas, menlo, courier, monospace, "Microsoft Yahei" !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">


  1. <dependencies>

  2. <dependency>

  3. <groupId>org.springframework.boot</groupId>

  4. <artifactId>spring-boot-autoconfigure</artifactId>

  5. </dependency>


  6. <dependency>

  7. <groupId>gank.spring.hello</groupId>

  8. <artifactId>gank</artifactId>

  9. <version>0.0.1-SNAPSHOT</version>

  10. <scope>provided</scope>

  11. </dependency>

  12. </dependencies>


</pre>


这里我们实现了一个简单的被自动配置的 jar--?gank,只有一个?GankApplicationRunner类,代码如下:


<pre style="margin: 0px; padding: 8px 0px 6px; max-width: 100%; box-sizing: border-box; word-wrap: break-word !important; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: 0.544px; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background: rgb(27, 25, 24); border-radius: 0px; overflow-y: auto; color: rgb(80, 97, 109); text-align: start; font-size: 10px; line-height: 12px; font-family: consolas, menlo, courier, monospace, "Microsoft Yahei" !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">


  1. @Slf4j

  2. public class GankApplicationRunner implements ApplicationRunner {

  3. public GankApplicationRunner() {

  4. log.info("初始化 GankApplicationRunner.");

  5. }

  6. public void run(ApplicationArguments args) throws Exception {

  7. log.info("Hello Spring! ");

  8. }

  9. }


</pre>


依赖和被配置的 jar 都做好了,我们可以开始编写 java Config 类了,在


gank-spring-boot-autoconfigure中,我们创建了?GankAutoConfiguration配置类,代码如下:


<pre style="margin: 0px; padding: 8px 0px 6px; max-width: 100%; box-sizing: border-box; word-wrap: break-word !important; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: 0.544px; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background: rgb(27, 25, 24); border-radius: 0px; overflow-y: auto; color: rgb(80, 97, 109); text-align: start; font-size: 10px; line-height: 12px; font-family: consolas, menlo, courier, monospace, "Microsoft Yahei" !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">


  1. @Configuration

  2. @ConditionalOnClass(GankApplicationRunner.class)

  3. public class GankAutoConfiguration {

  4. @Bean

  5. @ConditionalOnMissingBean(GankApplicationRunner.class)

  6. //配置文件中gank.enabled的值为true才创建,不存在默认为true

  7. @ConditionalOnProperty(name = "gank.enabled", havingValue = "true", matchIfMissing = true)

  8. public GankApplicationRunner gankApplicationRunner() {

  9. return new GankApplicationRunner();

  10. }

  11. }


</pre>


这里我们需要满足条件,即不存在 GankApplicationRunner 类实例,并且配置中的 gank.enabled 参数为 true,我们才会进行 GankApplicationRunner 的 bean 创建,自动配置类编写完毕后,我们在?resources目录下,创建?META-INF资源包,并且在该目录下创建?spring.factories文件,将我们刚才编写的 GankAutoConfiguration 配置类编写上去,如下:


<pre style="margin: 0px; padding: 8px 0px 6px; max-width: 100%; box-sizing: border-box; word-wrap: break-word !important; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: 0.544px; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background: rgb(27, 25, 24); border-radius: 0px; overflow-y: auto; color: rgb(80, 97, 109); text-align: start; font-size: 10px; line-height: 12px; font-family: consolas, menlo, courier, monospace, "Microsoft Yahei" !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">


  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

  2. gank.spring.hello.GankAutoConfiguration


</pre>


至此,我们的自动配置包编写完成,接下来我们创建一个 Spingboot-demo 工程,pom 依赖中我们将刚才的创建的两个工程进行依赖,pom 配置如下:


<pre style="margin: 0px; padding: 8px 0px 6px; max-width: 100%; box-sizing: border-box; word-wrap: break-word !important; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: 0.544px; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background: rgb(27, 25, 24); border-radius: 0px; overflow-y: auto; color: rgb(80, 97, 109); text-align: start; font-size: 10px; line-height: 12px; font-family: consolas, menlo, courier, monospace, "Microsoft Yahei" !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">


  1. <dependency>

  2. <groupId>gank.spring.hello</groupId>

  3. <artifactId>gank-spring-boot-autoconfigure</artifactId>

  4. <version>0.0.1-SNAPSHOT</version>

  5. </dependency>

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
SpringBoot自动配置原理及手动实现自动配置,35岁程序员半月4轮面试