序言
Spring Boot的出现,得益于“约定大于配置”的理念,没有繁琐的配置、难以集成的内容(大多数流行第三方技术都被集成),这是基于Spring 4.x提供的按条件配置Bean的能力。
自动配置作为SpringBoot的精髓,不仅仅是“面试”中会问到自动配置的原理,如果能理解自动配置的原理,将无往不利。
本文使用SpringBoot版本为:2.3.3
工作原理
@SpringBootApplication
首先,我们都知道SpringBoot项目运行标记 @SpringBootApplication
注解的类 的main方法来启动SpringBoot应用,那么从这里开始着手
@SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用。
在xxxApplication.java文件中查看@SpringBootApplication
注解(按住ctrl+点击注解)
1 | // SpringBootApplicaion.class |
@SpringBootConfiguration
:Spring Boot的配置类。- 标注在某个类上,表示这是一个Spring Boot的配置类。
- 里面有
@Configuration
:配置类上标注这个注解。
配置类就相当于配置文件,配置类也是容器中的一个组件。@Configuration
的底层也是@Component
- 里面有
- 标注在某个类上,表示这是一个Spring Boot的配置类。
@ComponentScan
:自动扫描,这里搭配excludeFilters表示跳过扫描- excludeFilters参考官方API
@EnableAutoConfiguration
:开启自动配置功能,明显这就是自动配置的重点,查看这个注解。
@EnableAutoConfiguration
这个注解也是一个派生注解,@AutoConfigurationPackage和@Import提供了自动配置功能。
其定义如下:
1 | // EnableAutoConfiguration.class |
@AutoConfigurationPackage
@AutoConfigurationPackage
: 自动配置包,查看其定义,可以看到导入了一个Registrar类。
@Import({Registrar.class}):Spring的底层注解@Import,给容器中导入一个组件;导入的组件由Registrar.class指定
查看Registrar类,有个registerBeanDefinitions方法
1 | // Registrar.class |
registerBeanDefinitions:获取bean的定义
- 参数一:metadata:获取启动类信息,即获取该类所在包(
@SpringBootApplication
标记的类所在包)。 - 参数二:registry:bean注册。
总结
从上述内容中我们可以得出结论:@AutoConfigurationPackage
的作用就是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器。
@Import({AutoConfigurationImportSelector.class})
这是自动配置的重点,精髓就在这里,首先查看AutoConfigurationImportSelector类,这个类是配置选择器,将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中。会给容器中导入非常多的自动配置类(xxxAutoConfiguration)。
可以看到一个selectImports()
方法
1 | // AutoConfigurationImportSelector.class |
如上,这方法的返回值是调用getAutoConfigurationEntry()
方法的结果-配置项信息,这个方法就在selectImports()
方法下方,定义如下:
1 | protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { |
这行代码返回的是已申请即将导入容器的配置项信息集合,进入getCandidateConfigurations()
方法。
1 | protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { |
有一行断言: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找到配置类。
然后进入loadFactoryNames()方法
1 | public static List<String> loadFactoryNames(Class<?> factoryType, ClassLoader classLoader){ |
具体细节在loadSpringFactories()
方法中,该方法就在下方。
1 | private static Map<String, List<String>> loadSpringFactories( ClassLoader classLoader) { |
从 “META-INF/spring.factories” 中获取资源,这与之前那句断言相对应。
spring.factories位置如上,在libraries的Maven: org.springframework.boot: spring-boot-autoconfigure.2.3.3.RELEASE下spring-boot-autoconfigure-2.3.3.RELEASE.jar!
文件夹的META-INF中
截取一段内容如上。
继续看loadSpringFactories()方法。
- 在外层while中会将文件数据转换成URL,然后将URL装换成Properties,Properties是一个Hashtable,最终配置文件中的数据将以key-value的形式存在Properties中的entry中,如下所示:
内层的while循环其实就是对Properties进行遍历,获取key,获取value,并且会将value的值按逗号分隔获取具体的每一个String,然后将这些数据存入result这个Map中。
key value org.springframework.context.ApplicationContextInitializer org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer org.springframework.context.ApplicationContextInitializer org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener org.springframework.context.ApplicationListener org.springframework.boot.autoconfigure.BackgroundPreinitializer 最后返回该保存了所有配置项信息的map,回到loadFactoryNames()方法
getOrDefault(…)方法是map的方法,用来判断当前map中是否包指定的key,有则返回对应的数据,这个key就是loadFactoryNames()第一个参数的某个Class的全路径名,往回loadFactoryNames方法上一层看
1 | default V getOrDefault(Object key, V defaultValue){ |
如图中所示,传进来的就是EnableAutoConfiguration,那自然这个key就是EnableAutoConfiguration的全路径,也就是会拿到配置文件(spring.factories)中EnableAutoConfiguration所对应的数据
这就是自动配置的配置的组件内容了。
自动配置原理总结
- SpringBoot启动的时候加载主配置类,开启了自动配置功能
@EnableAutoConfiguration
注解 - @EnableAutoConfiguration的作用:
- 利用
@Import({AutoConfigurationImportSelector.class})
给容器中导入一些组件 - 查看
AutoConfigurationImportSelector
类中的selectImports
方法 - 查看
selectImports
方法调用的getAutoConfigurationEntry
方法 - 发现通过方法中的
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes)
;获取候选的配置 - 查看
getCandidateConfigurations
方法,这个方法返回loadFactoryNames
方法的返回值 - 查看
loadFactoryNames
方法,发现返回loadSpringFactories
返回后通过getOrDefault
筛选的值 loadSpringFactories
方法中扫描META-INF/spirng.factories文件内容,将文件中的数据转换成URL,然后将URL装换成Properties,最后对Properties进行遍历,处理数据并存入result这个map中- 通过
getOrDefault
返回EnableAutoConfiguration.class
类(类名)对应的值,然后把他们添加在容器中
- 利用
总结起来就是:
- 将类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中;
- 每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中,用他们来做自动配置。
- 每一个自动配置类进行自动配置功能
自动配置类进行自动配置示例
以HttpEncodingAutoConfiguration
(Http编码自动配置)为例解释自动配置原理:
idea中双击shift搜索HttpEncodingAutoConfiguration
1 |
|
1 |
|
这些注解根据当前不同的条件判断,决定这个配置类是否生效
一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
配置tomcat接口就是一个应用
1 | 8081 = |
所有在配置文件中能配置的属性都是在xxxxProperties类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类
xxxxAutoConfigurartion:自动配置类,作用是给容器中添加组件
xxxxProperties:作用是封装配置文件中的相关属性;
SpringBoot重点:
- SpringBoot启动会加载大量配置类
- 编写时看需要的功能有没有默认写好的自动配置类
- 再看这个自动配置类写了哪些组件(有需要的组件,就不要配置了)
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,可以在配置文件中指定这些属性的值
@Conditional派生注解
自动配置类必须在一定的条件下才能生效的条件靠@Conditional派生注解来判断
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
打印配置报告
我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效没生效了;
1 | debug=true |
添加配置后运行项目就可以在配置台看到配置报告
报告中:
- CONDITIONS EVALUATION REPORT:自动配置报告
- Positive matches:自动配置类启用的
- Negative matches:没有启动,没有匹配成功的自动配置类
参考
SpringBoot+MyBatis+Druid