Skip to content

Commit 07604de

Browse files
committed
Springboot 系列(三)Spring Boot 自动配置
1 parent 11775b8 commit 07604de

1 file changed

Lines changed: 251 additions & 0 deletions

File tree

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
---
2+
title: Springboot 系列(三)Spring Boot 自动配置
3+
toc_number: false
4+
date: 2019-01-10 23:01:01
5+
url: springboot/springboot03-auto-config
6+
tags:
7+
- Springboot
8+
categories:
9+
- Springboot
10+
---
11+
12+
13+
> 注意:本 Spring Boot 系列文章基于 Spring Boot 版本 **v2.1.1.RELEASE** 进行学习分析,版本不同可能会有细微差别。
14+
15+
## 前言
16+
17+
![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/90be321394ddff7aa6dfdc9910888fda.png)
18+
19+
关于配置文件可以配置的内容,在 [Spring Boot 官方网站](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#common-application-properties)已经提供了完整了配置示例和解释。
20+
21+
可以这么说,Spring Boot 的一大精髓就是自动配置,为开发省去了大量的配置时间,可以更快的融入业务逻辑的开发,那么自动配置是怎么实现的呢?
22+
<!-- more -->
23+
## 1. `@SpringBootApplication`
24+
25+
跟着 Spring Boot 的启动类的注解 `@SpringBootApplication` 进行源码跟踪,寻找自动配置的原理。
26+
27+
```java
28+
@Target({ElementType.TYPE})
29+
@Retention(RetentionPolicy.RUNTIME)
30+
@Documented
31+
@Inherited
32+
@SpringBootConfiguration
33+
@EnableAutoConfiguration
34+
@ComponentScan(
35+
excludeFilters = {@Filter(
36+
type = FilterType.CUSTOM,
37+
classes = {TypeExcludeFilter.class}
38+
), @Filter(
39+
type = FilterType.CUSTOM,
40+
classes = {AutoConfigurationExcludeFilter.class}
41+
)}
42+
)
43+
public @interface SpringBootApplication {
44+
```
45+
46+
`@EnableAutoConfiguration` 开启自动配置。
47+
48+
`@ComponentScan` 开启注解扫描
49+
50+
从 `SpringBootApplication` 我们可以发现,这是一个简便的注解配置,它包含了自动配置,配置类,包扫描等一系列功能。
51+
52+
## 2. `@EnableAutoConfiguration`
53+
54+
继续跟踪,查看`@EnableAutoConfiguration` 源码,里面比较重要的是 `@Import` ,导入了一个翻译名为自动配置的选择器的类。这个类其实就是自动配置的加载选择器。
55+
56+
```java
57+
@Target({ElementType.TYPE})
58+
@Retention(RetentionPolicy.RUNTIME)
59+
@Documented
60+
@Inherited
61+
@AutoConfigurationPackage
62+
@Import({AutoConfigurationImportSelector.class})
63+
public @interface EnableAutoConfiguration {
64+
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
65+
66+
Class<?>[] exclude() default {};
67+
68+
String[] excludeName() default {};
69+
}
70+
71+
```
72+
73+
继续跟踪 `AutoConfigurationImportSelector.class` .在这个类有一个重要的方法 `getCandidateConfigurations`.用于加载 Spring Boot 配置的自动配置类。
74+
75+
`getAutoConfigurationEntry` 会筛选出有效的自动配置类。
76+
77+
```java
78+
protected AutoConfigurationEntry getAutoConfigurationEntry(
79+
AutoConfigurationMetadata autoConfigurationMetadata,
80+
AnnotationMetadata annotationMetadata) {
81+
if (!isEnabled(annotationMetadata)) {
82+
return EMPTY_ENTRY;
83+
}
84+
AnnotationAttributes attributes = getAttributes(annotationMetadata);
85+
List<String> configurations = getCandidateConfigurations(annotationMetadata,
86+
attributes);
87+
configurations = removeDuplicates(configurations);
88+
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
89+
checkExcludedClasses(configurations, exclusions);
90+
configurations.removeAll(exclusions);
91+
configurations = filter(configurations, autoConfigurationMetadata);
92+
fireAutoConfigurationImportEvents(configurations, exclusions);
93+
return new AutoConfigurationEntry(configurations, exclusions);
94+
}
95+
96+
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
97+
AnnotationAttributes attributes) {
98+
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
99+
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
100+
Assert.notEmpty(configurations,
101+
"No auto configuration classes found in META-INF/spring.factories. If you "
102+
+ "are using a custom packaging, make sure that file is correct.");
103+
return configurations;
104+
}
105+
```
106+
下图是 DEBUG 模式下筛选之后的结果,因为我只添加了 web 模块,所以只有 web 相关的自动配置。
107+
108+
![筛选过后的自动配置](https://user-images.githubusercontent.com/26371673/50733348-ec781400-11c6-11e9-8f0d-01797d797d69.png)
109+
110+
## 3. xxxAutoConfiguration 与 xxxProperties
111+
112+
在上面的 debug 里,我们看到了成功加载的自动配置,目前只看到了配置类,却还没有发现自动配置值,随便选择一个 `AutoConfiguration` 查看源码。
113+
114+
这里选择了 `ServletWebServerFactoryAutoConfiguration`.
115+
116+
```java
117+
@Configuration
118+
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
119+
//判断当前项目有没有这个类
120+
//CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
121+
@ConditionalOnClass(ServletRequest.class)
122+
//Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果
123+
//满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效
124+
@ConditionalOnWebApplication(type = Type.SERVLET)
125+
@EnableConfigurationProperties(ServerProperties.class)
126+
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
127+
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
128+
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
129+
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
130+
public class ServletWebServerFactoryAutoConfiguration {
131+
```
132+
133+
需要注意的是 `@EnableConfigurationProperties(ServerProperties.class)`.他的意思是启动指定类的
134+
`ConfigurationProperties`功能;将配置文件中对应的值和 `ServerProperties` 绑定起来;并把
135+
`ServerProperties` 加入到 IOC 容器中。
136+
137+
再来看一下 `ServerProperties` .
138+
139+
```java
140+
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
141+
public class ServerProperties {
142+
143+
/**
144+
* Server HTTP port.
145+
*/
146+
private Integer port;
147+
```
148+
149+
显而易见了,这里使用 ConfigurationProperties 绑定属性映射文件中的 server 开头的属性。结合默认配置
150+
```
151+
# 路径spring-boot-autoconfigure-2.1.1.RELEASE.jar
152+
# /META-INF/spring-configuration-metadata.json
153+
154+
{
155+
"name": "server.port",
156+
"type": "java.lang.Integer",
157+
"description": "Server HTTP port.",
158+
"sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties",
159+
"defaultValue": 8080
160+
}
161+
```
162+
达到了自动配置的目的。
163+
164+
## 4. 自动配置总结
165+
166+
1. SpringBoot 启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration
167+
2. @EnableAutoConfiguration 给容器导入META-INF/spring.factories 里定义的自动配置类。
168+
3. 筛选有效的自动配置类。
169+
4. 每一个自动配置类结合对应的 xxxProperties.java 读取配置文件进行自动配置功能 。
170+
171+
## 5. 配置类
172+
173+
通过自动配置,我们发现已经帮我们省去了大量的配置文件的编写,那么在自定义配置的时候,我们是不是需要编写XML呢?Spring boot 尽管可以使用 `SpringApplication`XML 文件进行配置,但是我们通常会使用 `@Configuration` 类进行代替,这也是官方推荐的方式。
174+
175+
### 5.1 XML配置
176+
177+
定义 helloService Bean.
178+
179+
```xml
180+
<?xml version="1.0" encoding="UTF-8"?>
181+
<beans xmlns="http://www.springframework.org/schema/beans"
182+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
183+
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
184+
185+
<bean id="helloService" class="net.codingme.boot.service.HelloService"></bean>
186+
187+
</beans>
188+
```
189+
190+
引入配置。
191+
192+
```java
193+
@ImportResource(value = "classpath:spring-service.xml")
194+
@SpringBootApplication
195+
public class BootApplication {
196+
197+
public static void main(String[] args) {
198+
SpringApplication.run(BootApplication.class, args);
199+
}
200+
}
201+
```
202+
203+
### 5.2 注解配置
204+
205+
此种方式和上面的XML配置是等效的,也是官方推荐的方式。`@Configuration` 注解的类(要在扫描的包路径中)会被扫描到。
206+
207+
```java
208+
/**
209+
* <p>
210+
* 配置类,相当于传统Spring 开发中的 xml-> bean的配置
211+
*
212+
* @Author niujinpeng
213+
* @Date 2018/12/7 0:04
214+
*/
215+
@Configuration
216+
public class ServiceConfig {
217+
218+
/**
219+
* 默认添加到容器中的 ID 为方法名(helloService)
220+
*
221+
* @return
222+
*/
223+
@Bean
224+
public HelloService helloService() {
225+
return new HelloService();
226+
}
227+
}
228+
```
229+
230+
231+
232+
## 6. 附录
233+
234+
| @Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
235+
| ------------------------------- | ------------------------------------------------ |
236+
| @ConditionalOnJava | 系统的java版本是否符合要求 |
237+
| @ConditionalOnBean | 容器中存在指定Bean|
238+
| @ConditionalOnMissingBean | 容器中不存在指定Bean|
239+
| @ConditionalOnExpression | 满足SpEL表达式指定 |
240+
| @ConditionalOnClass | 系统中有指定的类 |
241+
| @ConditionalOnMissingClass | 系统中没有指定的类 |
242+
| @ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
243+
| @ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
244+
| @ConditionalOnResource | 类路径下是否存在指定资源文件 |
245+
| @ConditionalOnWebApplication | 当前是web环境 |
246+
| @ConditionalOnNotWebApplication | 当前不是web环境 |
247+
| @ConditionalOnJndi | JNDI存在指定项 |
248+
249+
250+
251+
文章代码已经上传到 GitHub [Spring Boot 自动配置](https://github.com/niumoo/springboot/tree/master/springboot-config)。

0 commit comments

Comments
 (0)