引言
传统的Spring项目,需要写一大堆的的xml配置文件,或者以注解的形式定义大量的配置类。而SpringBoot引入了自动配置的特性,简化了这些繁琐的配置过程。
SpringBoot遵循“约定优于配置”的原则,对一些常规的配置项作为默认配置,减少或不使用xml配置,让项目可以很快速地搭建运行起来。SpringBoot还为许多常用框架封装了starter,只需要引入一个starter,在不需要配置或只需很少的配置情况下,就能够使用这个框架的功能。
本文主要分析SpringBoot是怎么自动配置的。
源码分析
@SpringBootApplication
我们知道SpringBoot项目启动类上都会标注@SpringBootApplication
,SpringBoot的自动配置也要从这个注解开始讲起。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 (ElementType.TYPE)
(RetentionPolicy.RUNTIME)
(excludeFilters = {
(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
(type = FilterType.CUSTOM,
classes = AutoConfigurationExcludeFilter.class) })
public SpringBootApplication {
//省略...
}
@SpringBootApplication
是一个复合注解,它包含了3个重要的注解:
- @SpringBootConfiguration:该注解表示这是一个 SpringBoot 的配置类。
- @ComponentScan:开启组件扫描。这是Spring的底层注解,作用是Spring会扫描basePackages属性指定的包下带有
@Component
注解的类,为这些类创建代理对象,并注入到IOC容器中。@Service
、@Controller
等这些注解包含了@Component
注解的语义,所以也会被扫描。 - @EnableAutoConfiguration:开启自动配置。
自动配置是就是通过@EnableAutoConfiguration
这个注解实现的,所以接着分析@EnableAutoConfiguration
。
@EnableAutoConfiguration
1 | (ElementType.TYPE) |
@EnableAutoConfiguration
也是一个复合注解,包含了两个重要注解:
@AutoConfigurationPackage
:表示注解的这个类所在包及其子包中的将通过AutoConfigurationPackages
类自动注册到Spring容器中。@Import(AutoConfigurationImportSelector.class)
:@Import
是Spring的底层注解,通过快速导入的方式将实例注册到Spring的IOC容器中。这里导入了一个AutoConfigurationImportSelector
实例。
小总结:@AutoConfigurationPackage
的功能是自动扫描注册bean;@AutoConfigurationImportSelector
的功能是自动配置。
AutoConfigurationImportSelector
AutoConfigurationImportSelector
类中有个重要的方法getAutoConfigurationEntry()
,Spring Boot项目在启动的时候会调用该方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
* of the importing {@link Configuration @Configuration} class.
* @param autoConfigurationMetadata the auto-configuration metadata
* @param annotationMetadata the annotation metadata of the configuration class
* @return the auto-configurations that should be imported
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//获取所有候选配置
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//去重
configurations = removeDuplicates(configurations);
//移除需要排除的组件
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
//扫描类路径下的所有META-INF/spring.factories文件,并返回自动配置类的全类名
List<String> 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;
}
SpringFactoriesLoader
1 |
|
根据上面的分析,spring.factories是一个关键的文件,在很多starter里都会带一个spring.factories文件,里面都是一些配置信息,格式为key-value形式,多个Value时使用英文逗号“,”隔开。
SpringBoot项目都会自动引入spring-boot-autoconfigure
jar包,这个jar里的spring.factories文件中很多关于类的配置,最终这些key-value会保存到一个MultiValueMap<String, String>
中(interface MultiValueMap<K, V> extends Map<K, List<V>>
),然后在经过去重、对要排除的配置项过滤等操作,得到最终的结果。(@SpringBootApplication
和@EnableAutoConfiguration
注解都可以通过exclude属性来指定需要排除掉那些自动配置项)。
而真正使自动配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration。这个key的value包含Spring Boot中可能会用到的许多自动配置类的全类名。
上面的urls表示的是如果引入的jar里有多个spring.factories文件,urls里存储的是多个spring.factories文件的绝对路径。比如spring-boot-autoconfigure
下的spring.factories文件只包含了基本的自动配置文件,当引入了Spring Cloud的一些组件之后,比如Feign、Ribbon、Hystrix之后,都会有对应的spring.factories文件被引入。
1 | # Auto Configure |
这些 xxxAutoConfiguration 这样的类如果在最终在上面map中,那最终会被实例化加入到spring容器中。
这就是Springboot自动配置之源,也就是自动配置的开始。
示例分析
下面以其中一个自动配置类HttpEncodingAutoConfiguration
(Http编码自动配置)为例解释自动配置原理。
HttpEncodingAutoConfiguration
1 | //表示这是Spring的一个配置类 |
下面分析一下这个类上的几个注解:
@EnableConfigurationProperties(HttpProperties.class)
使指定类的ConfigurationProperties功能生效(此处是HttpProperties
类)。我们知道如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在spring容器中是获取不到 properties 配置文件转化的bean。说白了,此处的@EnableConfigurationProperties(HttpProperties.class)
可以让HttpProperties
获取到properties或yml配置文件里的值,并且生成一个HttpProperties
实例注入到spring容器中。@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
这个注解基于Spring的底层注解:@Conditional
。表示这个配置类里的配置生效还需要一个条件:实在一个web应用中,并且是一个servlet的web应用。@ConditionalOnClass(CharacterEncodingFilter.class)
表示类路径下要有CharacterEncodingFilter
这个类。@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
表示配置文件中要有spring.http.encoding.enabled
这个配置项,并且等于havingValue的值或者havingValue的值为空且配置的值不为空才生效。matchIfMissing的意思缺少该配置属性时是否可以加载。如果为true,没有该配置属性时也会正常加载;反之则不会生效。关于havingValue的使用示例如下:
havingValue的规则举例
属性值 | havingValue=”” | havingValue=”true” | havingValue=”false” | havingValue=”abc” |
---|---|---|---|---|
“true” | yes | yes | no | no |
“false” | no | no | yes | no |
“abc” | yes | no | no | yes |
在HttpEncodingAutoConfiguration
中配置了一个CharacterEncodingFilter
实例,这个过滤器里的参数是从properties
属性获取。另外一点就是,向spring容器中注入的类,如果类只有一个构造器,那么构造器的参数会从容器中反向注入。
HttpProperties
1 | "spring.http") (prefix = |
HttpEncodingProperties
类上面标注了@ConfigurationProperties
注解,实例化时会从配置文件中获取指定的值和bean的属性进行绑定。HttpProperties示例持有一个Encoding变量,Encoding中有4个属性:charset、force、forceRequest和forceResponse,所以我们在配置文件中可以配置的就是这4个参数。
小结论:所有在配置文件中能配置的属性都是在 xxxxProperties 类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类。配置文件中的提示功能是来自于spring-configuration-metadata.json
文件。
再回过来看HttpEncodingAutoConfiguration
中配置的CharacterEncodingFilter
示例,这个过滤器里的参数就是我们在配置文件中1
2
3
4
5
6
7
8
9
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
总结
- SpringBoot启动会加载大量的自动配置类;
- 首先,看我们需要的功能Spring Boot有没有已经写好的自动配置类;
- 如果有,就再看这个自动配置类中到底配置了哪些组件,如果有这个组件了,就在配置文件中按照规则来配置。
- 如果没有我们需要的组件,那么就需要我们自己来写一个配置类来配置需要的功能组件。
拓展
@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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//启用了的自动配置类
Positive matches:
-----------------
CodecsAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer' (OnClassCondition)
CodecsAutoConfiguration.JacksonCodecConfiguration matched:
- @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)
//没有启用的自动配置类
Negative matches:
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)
AopAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition)