【spring】 spring auto configuration 的使用与实现

从源码分析 spring 如何实现 auto configuration

前言

spring-boot auto configuration 可以使得 spring 可以根据 classpath 有哪些 class, 有哪些 property 等来猜测用户需要哪些 bean, 并生成这些 bean 的实例。

实现

auto configuration 功能由两个功能实现:一个是 spring bean factory, 用于读取 spring.factories 文件加载 Configuration 类;另一个是相当于 @Import 注解将 spring bean factory 配置的类导入并解析其中的配置。

auto configuration 功能由 @EnableAutoConfiguration 注解打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
Class<?>[] exclude() default {};

String[] excludeName() default {};
}

// @AutoConfigurationPackage 定义如下
...
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

由 @EnableAutoConfiguration 的定义可以看出 auto configuration 功能主要由 AutoConfigurationImportSelector 和 AutoConfigurationPackages.Registrar 两个类实现

AutoConfigurationImportSelector 是一个 DeferredImportSelector,DeferredImportSelector 是在所有 @Configuration 类处理后再进行处理的 ImportSelector。 AutoConfigurationImportSelector 的所有用户定义的 @Configuration 类处理之后,从 spring.factories 中加载 @Configuration 类。

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
public class AutoConfigurationImportSelector implements DeferredImportSelector,
BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware,
Ordered {

...

// DeferredImportSelector 接口定义的方法, 返回一个 DeferredImportSelector.Group
@Override
public Class<? extends Group> getImportGroup() {
return AutoConfigurationGroup.class;
}

...

// AutoConfigurationImportSelector 的内部类, 实现 DeferredImportSelector.Group 接口
private static class AutoConfigurationGroup implements DeferredImportSelector.Group,
BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {

...

@Override
public void process(AnnotationMetadata annotationMetadata,
DeferredImportSelector deferredImportSelector) {

Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector ...);

// 从 spring.factories 加载出 EnableAutoConfiguration 类名
// 并根据 Configuration 类的条件过滤
AutoConfigurationEntry autoConfigurationEntry =
((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);

// 保存到 autoConfigurationEntries
// 之后 autoConfigurationEntries 中保存的类都当成 @Import 的类再进行处理
this.autoConfigurationEntries.add(autoConfigurationEntry);

for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
}
}

示例

使用 auto configuration 对 redis 进行配置。 在使用 spring-boot 整合 redis 时,只需要通过 maven 引入 spring-boot-starter-data-redis 依赖,然后配置 spring.redis.host,spring.redis.port(单机)或spring.redis.cluster.nodes (集群) 就可以使用。 原理是 spring-boot 默认会引入 spring-boot-autoconfigure, 这个 jar 包含了大部分像用的 java 组件的 auto configuration 配置。 例如针对 redis, spring-boot-autoconfigure 在它的 spring.factories 文件中有如下定义

1
2
3
4
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
...
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
...

这样 spring 的 auto configuration 机制就会在启动过程中处理 RedisAutoConfiguration

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
50
51
52
53
54
55
56

// RedisAutoConfiguration 是在 spring.factories 中的配置项
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
// 分别引入 Lettuce 和 Jedis 配置
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

// 定义 template
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
...
}

@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
...
}
}

// Lettuce 相关配置
@Configuration
@ConditionalOnClass(RedisClient.class)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
...

@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public LettuceConnectionFactory redisConnectionFactory(ClientResources clientResources)
throws UnknownHostException {
...
}

...
}

// Jedis 相关配置
@Configuration
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
class JedisConnectionConfiguration extends RedisConnectionConfiguration {

...

@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
...
}

...
}

使 RedisAutoConfiguration 生效的条件是 @ConditionalOnClass(RedisOperations.class), 表示当 classpath 中存在类 RedisOperations 时生效。@EnableConfigurationProperties(RedisProperties.class) 表示它会使用到 RedisProperties 类,spring 会注册一个 RedisProperties 实例到容器并给它注入相关的属性。

RedisAutoConfiguration 定义了 redisTemplate 和 stringRedisTemplate, 都需要 RedisConnectionFactory 作参数, 而 LettuceConnectionConfiguration 和 JedisConnectionConfiguration 就是用来定义 RedisConnectionFactory 的: 如果 classpath 中存在 RedisClient, 就使用 lettuce, 如果存在 GenericObjectPool 等就使用 jedis。在创建 RedisConnectionFactory 实例时,会根据配置的是 spring.redis.host 还是 spring.redis.cluster.nodes 来决定是使用单机模式还是集群模式.