引言
最近有个读者在面试,面试中被问到了这样一个问题“看你我的项目中用到了 springboot
,你说下springboot
的主动配置是怎么实现的?”这应该是一个 springboot
外面最最常见的一个面试题了。上面咱们就来带着这个问题一起解剖下 springBoot
的主动配置原理吧。
SpringMvc 和 SpringBoot 比照
首先咱们回顾下原来搭建一个 springmvc
的hello-word
的 web
我的项目(xml
配置的)咱们是不是要在 pom
中导入各种依赖,而后各个依赖有可能还会存在版本抵触须要各种排除。当你历尽含辛茹苦的把依赖解决了,而后还须要编写 web.xml、springmvc.xml
配置文件等。咱们只想写 个 hello-word
我的项目而已,确把一大把的工夫都花在了配置文件和 jar
包的依赖下面。大大的影响了咱们开发的效率,以及加大了 web
开发的难度。为了简化这简单的配置、以及各个版本的抵触依赖关系,springBoot
就应运而生。咱们当初通过 idea
创立一个 springboot
我的项目只有分分钟就解决了,你不须要关怀各种配置(根本实现零配置)。让你真正的实现了开箱即用。SpringBoot
帮你节约了大量的工夫去陪女朋友,不对程序员怎么会有女朋友呢?(没有的话也是能够 new 一个的 )它的呈现不仅能够让你把更多的工夫都花在你的业务逻辑开发上,而且还大大的升高了web
开发的门槛。所以 SpringBoot
还是比拟善解人衣的, 错啦错啦是善解人意,晓得开发人员的痛点在哪。
SpringBoot 主动配置加载
既然 Springboot
只管这么好用,然而作为一个使用者,咱们还是比拟好奇它是怎么帮咱们实现开箱即用的。Spring Boot
有一个全局配置文件:application.properties 或 application.yml
。在这个全局文件外面能够配置各种各样的参数比方你想改个端口啦 server.port
或者想调整下日志的级别啦统统都能够配置。更多其余能够配置的属性能够参照官网。https://docs.spring.io/spring…
![
](https://img-blog.csdnimg.cn/2…
这么多属性,这些属性在我的项目是怎么起作用的呢?SpringBoot
我的项目看下来啥配置也没有,配置”(application.properties 或 application.yml
除外),既 然从配置下面找不到突破口,那么咱们就只能从启动类下面找入口了。启动类也就一个赤裸裸的一个 main
办法,类下面仅有一个注 SpringBootApplication
这个注解是 Spring Boot
我的项目必不可少的注解。那么主动配置原理肯定和这个注解有着千头万绪的分割!咱们上面来一起看看这个注解吧。
@SpringBootApplication 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
这里最下面四个注解的话没啥好说的,基本上本人实现过自定义注解的话,都晓得别离是什么意思。
-
@SpringBootConfiguration
继承自@Configuration
,二者性能也统一,标注以后类是配置类。 -
@ComponentScan
用于类或接口上次要是指定扫描门路,跟 Xml 外面的<context:component-scan base-package="" />
配置一样。springboot
如果不写这个扫描门路的话,默认就是启动类的门路。 @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
这个注解咱们重点看下 AutoConfigurationImportSelector
这个类 getCandidateConfigurations
这个办法外面通过 SpringFactoriesLoader.loadFactoryNames()
扫描所有具备 META-INF/spring.factories
的jar
包(spring.factories 咱们能够了解成 Spring Boot
本人的 SPI
机制)。spring-boot-autoconfigure-x.x.x.x.jar
里就有一个 spring.factories 文件。spring.factories
文件由一组一组的 Key = value
的模式,其中一个 key
是 EnableAutoConfiguration 类的全类名,而它的 value 是一个以 AutoConfiguration
结尾的类名的列表,有 redis、mq
等这些类名以逗号分隔。
咱们在回到 getAutoConfigurationEntry
这个办法当执行完 getCandidateConfigurations
这个办法的时候咱们能够看到此时总共加载了 127
个主动配置类。
这些类难道都要加载进去吗?springboot
还是没有那么傻的,它提倡的话是按需加载。
- 它会去掉反复的类
- 过滤掉咱们配置了
exclude
注解的类上面配置就会过滤掉RestTemplateAutoConfiguration
这个类
- 通过下面的解决,剩下的这写主动配置的类如果要起作用的话,是须要满足肯定的条件的。这些条件的满足的话
spring boot
是通过条件注解来实现的。
@ConditionalOnBean:当容器里有指定 Bean 的条件下
@ConditionalOnClass:当类门路下有指定的类的条件下
@ConditionalOnExpression:基于 SpEL 表达式为 true 的时候作为判断条件才去实例化
@ConditionalOnJava:基于 JVM 版本作为判断条件
@ConditionalOnJndi:在 JNDI 存在的条件下查找指定的地位
@ConditionalOnMissingBean:当容器里没有指定 Bean 的状况下
@ConditionalOnMissingClass:当容器里没有指定类的状况下
@ConditionalOnWebApplication:以后我的项目时 Web 我的项目的条件下
@ConditionalOnNotWebApplication:以后我的项目不是 Web 我的项目的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类门路是否有指定的值
@ConditionalOnOnSingleCandidate:当指定 Bean 在容器中只有一个,或者有多个然而指定首选的 Bean
这些注解都组合了 @Conditional
注解,只是应用了不同的条件组合最初为 true 时才会去实例化须要实例化的类,否则疏忽过滤掉。咱们在回到代码能够看到通过了条件判断过滤后咱们剩下符合条件的主动配置类只剩 23 个了。其余的都是因为不满足条件注解而被过滤了。
如果咱们想晓得哪些主动配置类被过滤了,是因为什么起因被过滤了,以及加载了哪些类等。spring boot
都为咱们记录了日志。还是十分贴心的。咱们能够调整下咱们日志的级别改为 debug
。而后咱们就能看到以下日志了
这里就截取了局部日志。总共别离有上面四局部日志:
-
Positive matches
:@Conditional
条件为真,配置类被 Spring 容器加载。 -
Negative matches:
@Conditional
条件为假,配置类未被 Spring 容器加载。 -
Exclusions
:咱们明确了不须要加载的类。比方在下面启动类配置的RestTemplateAutoConfiguration
类 -
Unconditional classes
:主动配置类不蕴含任何类级别的条件,也就是说,类始终会被主动加载。
主动配置失效
咱们以 ServletWebServerFactoryAutoConfiguration
配置类为例,解释一下全局配置文件中的属性如何失效,比方:server.port=88
,是如何失效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat
)。
// 标记为配置类
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
// 如果有 ServletRequest.class 才会失效
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
// 把 @ConfigurationProperties 注解的类注入为 Spring 容器的 Bean。@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
咱们能够发现 EnableConfigurationProperties
注解外面配置的ServerProperties.class
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
/**
* Server HTTP port.
*/
private Integer port;
在这个类上有一个注解:@ConfigurationProperties
,它的作用就是从配置文件中绑定属性到对应的 bean 上 (也就是把咱们application.properties
对应的 server.port 映射到 ServerProperties
类中的port
属性)而 @EnableConfigurationProperties
这个注解就是把曾经绑定了属性的 bean
(ServerProperties
)注入到spring
容器中(相当于 @Component
注解一样)。
所有在配置文件中能配置的属性都是在 xxxxPropertites
类中封装着,配置文件能配置什么就能够参照某个性能对应的这个属性类。
到当初为止应该能答复文章结尾的那个问题了,面试的时候应该不须要答复的这么具体能够参考下以下答案:
Spring Boot 启动的时候会通过 @EnableAutoConfiguration 注解找到 META-INF/spring.factories 配置文件中的所有主动配置类,并对其进行加载,而这些主动配置类都是以 AutoConfiguration 结尾来命名的,它实际上就是一个 JavaConfig 模式的 Spring 容器配置类,它能通过以 Properties 结尾命名的类中获得在全局配置文件中配置的属性如:server.port,而 XxxxProperties 类是通过 @ConfigurationProperties 注解与全局配置文件中对应的属性进行绑定的。
在网上找了一张图,基本上把主动拆卸的流程给说分明了。
总结
-
SpringBoot
启动会加载大量的主动配置类(通过“SPI
”的形式),而后会依据条件注解保留一些须要的类。 - 咱们新引入一个组件,能够先看看 springBoot 是否曾经有默认的提供。
-
SpringBoot
根本实现了“零配置“,并且开箱即用。
完结
- 因为本人满腹经纶,难免会有纰漏,如果你发现了谬误的中央,还望留言给我指出来, 我会对其加以修改。
- 如果你感觉文章还不错,你的转发、分享、赞叹、点赞、留言就是对我最大的激励。
- 感谢您的浏览, 非常欢送并感谢您的关注。
站在伟人的肩膀上摘苹果:
https://docs.spring.io/spring…
https://blog.csdn.net/u014745…
https://afoo.me/posts/2015-07…