SpringBoot自动配置原理及手动实现自动配置

之前我们学习了SpringBoot中的配置文件及外部化配置,了解了SpringBoot对于配置文件的功能支持与增强,本篇我们将要来学习SpringBoot的自动配置原理及手动实现自动配置。

数据库依赖引起的bug

我们很多人在第一次使用SpringBoot的时候,往往对其原理认知不足,或者简单的了解以后就开始入门使用,往往最常见的就是使用SpringBoot添加一个持久化框架的依赖,用来尝试操作数据库,比如:

<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, &quot;Microsoft Yahei&quot; !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">

1.  `<dependency>`

2.  `<groupId>org.springframework.boot</groupId>`

3.  `<artifactId>spring-boot-starter-jdbc</artifactId>`

4.  `</dependency>`

</pre>

接着我们启动SpringBoot,异常出现了,SpringBoot居然启动不了?

![image](https://upload-images.jianshu.io/upload_images/15590149-b9134b30001ed942?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

看报错信息,似乎是我没有配置url导致数据源注入失败,但是我仅仅是引入了一个依赖,什么操作也没有啊?看来SpringBoot帮我们引入的db框架尝试进行自动注入了,我们在Spring中如何配置一个数据源的呢?

<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, &quot;Microsoft Yahei&quot; !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">

1.  `<beanid="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource"`

2.  `destroy-method="close">`

3.  `<propertyname="driverClass"value="${driverClass}"/>`

4.  `<propertyname="jdbcUrl"value="${jdbcUrl}"/>`

5.  `<propertyname="user"value="${user}"/>`

6.  `<propertyname="password"value="${password}"/>`

7.  `</bean>`

</pre>

那么岂不是代表着,我们仅仅引入一个 spring-boot-starter-jdbc 的依赖,SpringBoot帮我们自动配置了一个dataSource的bean?百度了一下,我们知道,可以在springboot启动类中排除datasource自动配置类来解决此问题,如下:

<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, &quot;Microsoft Yahei&quot; !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">

1.  `@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)`

</pre>

这样就可以使得SpringBoot启动的时候不去自动配置数据源,但是SpringBoot是如何实现的自动配置呢?

SpringBoot中配置Bean的几种方式

在思考这个问题之前,我们先来看看,在SpringBoot中我们如果要配置一个Bean,有哪几种方式:

@Service/@Component

在SpringBoot中不建议使用xml配置创建实例Bean,而在Spring中我们知道,如果不使用xml配置,我们往往可以在一个类上使用 @Service或者 @Component注解,Spring启动的时候,Spring容器会自动扫描带有此注解的类,并且注册该类的实例到Spring容器中,在SpringBoot也完全支持此种方式:

@Configuration /@Bean

在SpringBoot中使用了注解驱动的方式,可以给类添加 @Configuration注解标记此类为配置类,也可以给类添加 @Bean注解,代表创建当前实例bean到ioc容器中,需要注意的是使用 @Configuration注解需要配置在SpringBoot启动类所在的包下或者其子包下,否则将无法被扫描装载进入容器中,使用此方式创建bean如下:

<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, &quot;Microsoft Yahei&quot; !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">

1.  `@Configuration`

2.  `public class BeanConfig{`

3.  `@Bean`

4.  `public ClockService clockService() {`

5.  `return new ClockService();`

6.  `}`

7.  `}`

</pre>

@Import注解

相信在使用SpringBoot过程中经常会发现 @EnableScheduling@EnableCaching 等注解,如果我们点进此类注解查看源码,发现使用的都是@Import注解来实现某个功能开启的,使用此注解也可以在SpringBoot注入的时候导入一个Bean,例如:

<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, &quot;Microsoft Yahei&quot; !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">

1.  `@SpringBootApplication(exclude =  DataSourceAutoConfiguration.class)`

2.  `@Import(ClockService.class)`

3.  `public  class  AutoConfDemoApplication  {`

5.  `public  static  void main(String[] args)  {`

6.  `SpringApplication.run(AutoConfDemoApplication.class, args);`

7.  `}`

8.  `}`

</pre>

xml配置/@Conditional

除此之外,SpringBoot依赖保留了Spring的xml配置方式,并且SpringBoot使用了注解驱动开发的方式,同时也可以使用@Conditional开头的条件过滤注解来配置不同的bean。

自动配置源码分析

了解了SpringBoot中的bean配置以后,我们来看看springboot最核心的注解 @SpringBootApplication ,我们知道每个SpringBoot的启动类上都会标注这个注解,代表此类为主要运行类,似乎加上了这个注解以后,我们的一些bean就能自动的配置注入进ioc容器中了,到底是如何实现的呢?我们来点开此注解,看看内部的实现:

![image](https://upload-images.jianshu.io/upload_images/15590149-6d8de75528e30233?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

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

![image](https://upload-images.jianshu.io/upload_images/15590149-17f29c16a9ff08c0?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可以看到其中有一个 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, &quot;Microsoft Yahei&quot; !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, &quot;Microsoft Yahei&quot; !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.  `}`

7.  `try  {`

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

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

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

11.  `ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));`

12.  `result =  new  LinkedMultiValueMap<>();`

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

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

15.  `UrlResource resource =  new  UrlResource(url);`

16.  `Properties properties =  PropertiesLoaderUtils.loadProperties(resource);`

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

18.  `List<String> factoryClassNames =  Arrays.asList(`

19.  `StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));`

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

21.  `result.addAll((String) entry.getKey(), factoryClassNames);`

22.  `}`

23.  `}`

24.  `cache.put(classLoader, result);`

25.  `return result;`

26.  `}`

27.  `catch  (IOException ex)  {`

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

29.  `FACTORIES_RESOURCE_LOCATION +  "]", ex);`

30.  `}`

31.  `}`

</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: consolas, menlo, courier, monospace, &quot;Microsoft Yahei&quot; !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.  `<!--将需要被自动配置进去的类所在的jar依赖进来-->`

7.  `<dependency>`

8.  `<groupId>gank.spring.hello</groupId>`

9.  `<artifactId>gank</artifactId>`

10.  `<version>0.0.1-SNAPSHOT</version>`

11.  `<scope>provided</scope>`

12.  `</dependency>`

13.  `</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, &quot;Microsoft Yahei&quot; !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.  `}`

7.  `public  void run(ApplicationArguments args)  throws  Exception  {`

8.  `log.info("Hello Spring! ");`

9.  `}`

10.  `}`

</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, &quot;Microsoft Yahei&quot; !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, &quot;Microsoft Yahei&quot; !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, &quot;Microsoft Yahei&quot; !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>`

7.  `<dependency>`

8.  `<groupId>gank.spring.hello</groupId>`

9.  `<artifactId>gank</artifactId>`

10.  `<version>0.0.1-SNAPSHOT</version>`

11.  `</dependency>`

</pre>

在这里,我们给application.properties文件中配置一下:

<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, &quot;Microsoft Yahei&quot; !important; border-width: 1px !important; border-style: solid !important; border-color: rgb(226, 226, 226) !important;">

1.  `#这里不配置默认也是true`

2.  `gank.enabled=true`

</pre>

接着,我们启动当前的demo工程,当SpringBoot启动完毕,我们查看控制台,就会发现我们编写的log已经输出,可见自动动手编写的自动配置已经实现!

注:这里需要注意一下,demo工程的启动类所在的包名一定要和 gank-spring-boot-autoconfigure工程的一样,或者是其父包名,否则配置类无法生效。

最后,欢迎大家在下方评论区留下你的想法,如果觉得本文不错,那就给小编点个赞吧!

发布了152 篇原创文章 · 获赞 38 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/EnjoyEDU/article/details/104944220