|
| 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 | + |
| 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 | + |
| 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