SpringBoot03-自动配置

SpringBoot自动配置原理

Spring注解方式整合redis

redis.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
redis.hostname=127.0.0.1

redis.port=6379

redis.database=0

redis.pool.maxActive=600

redis.pool.maxIdle=300

redis.pool.maxWait=3000

redis.pool.testOnBorrow=true
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
57
58
59
60
61
62
@Configuration
@PropertySource("classpath:redis.properties")
public class RedisConfig {
@Value("${redis.hostname}")
private String hostname;

@Value("${redis.port}")
private int port;

@Value("${redis.database}")
private int database;

@Value("${redis.pool.maxActive}")
private int maxActive;

@Value("${redis.pool.maxIdle}")
private int maxIdle;

@Value("${redis.pool.maxWait}")
private int maxWait;

@Value("${redis.pool.testOnBorrow}")
private boolean testOnBorrow;


@Bean
public JedisPoolConfig jedisPoolConfig(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(maxActive);
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxWaitMillis(maxWait);
poolConfig.setTestOnBorrow(testOnBorrow);
return poolConfig;
}

@Bean
public JedisConnectionFactory jedisConnectionFactory(){
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(hostname);
factory.setPort(port);
factory.setPoolConfig(jedisPoolConfig());
factory.setDatabase(database);
return factory;

}

@Bean
public RedisTemplate<String,Object> redisTemplate(){
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

RedisTemplate<String,Object> redisTemplate = new RedisTemplate();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jacksonSeial);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jacksonSeial);
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
}

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>

SpringBoot自动装配整合redis

1
2
3
4
5
6
7
8
9
10
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setDefaultSerializer(new Jackson2JsonRedisSerializer(Object.class));
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

传统整合和springboot自动装配对比

image-20210816124943642

传统整合:

​ 自己引入依赖

​ 版本冲突

​ 配置复杂

自动装配

​ 起步依赖

​ 自动配置

​ 约定 > 配置 > 编码

SpringBoot自动配置原理

@Condition

​ Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以根据系统中已有组件实现动态选择性的创建 Bean 操作,从而达到自动配置的目的。

思考:

​ SpringBoot是如何知道要创建哪个Bean的?比如SpringBoot是如何知道要创建RedisTemplate的?

SpringBoot引入spring-data-redis坐标后,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
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

}

模仿使用

判断是否存在哪些类而注入当前类

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
1、定义一个Condtion实现类
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Map<String, Object> map =
annotatedTypeMetadata.getAnnotationAttributes(MyConditionOnClass.class.getName());
String[] className = (String[]) map.get("value");
System.out.println(className);
for (String s : className) {
try {
Class<?> aClass = Class.forName(s);
} catch (ClassNotFoundException e) {
return false;
}
}
return true;
}
}
2、声明一个注解,用来接收要判断是否存在的类
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(value = MyCondition.class)
public @interface MyConditionOnClass {
String [] value();
}

3
@Configuration
public class AutoConfig {

@Bean
@MyConditionOnClass("com.study.springboot.Springboot02AutoconfigApplication")
public Person person(){
return new Person("小帅");
}

public class Person{
private String name;

public Person(String name) {
this.name = name;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
}

小结

自定义条件:

​ 第一步 定义条件类:

​ 自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回boolean值 。 matches 方法两个参数:

​ context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。

​ metadata:元数据对象,用于获取注解属性。

​ 第二步 充当判断条件:

​ 在初始化Bean时,使用 @Conditional(条件类.class)注解

SpringBoot 提供的常用条件注解:

ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean

ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean

ConditionalOnMissingBean:判断环境中没有对应Bean实例才初始化Bean

@Enable*

​ SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载

@Import注解

​ @Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:

  1. 导入配置类(最先注入)
  2. 导入 ImportSelector 实现类。一般用于加载配置文件中的类(返回new String[] 可以返回配置文件的key)
  3. 直接注入类
  4. 导入 ImportBeanDefinitionRegistrar 实现类(最后注入)。

@EnableAutoConfiguration

  1. @EnableAutoConfiguration 注解内部使用 @Import(AutoConfigurationImportSelector.class)来加载配置类。
  2. 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当 SpringBoot 应用启动时,会自动加载这些配置类,初始化Bean
  3. 并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean

在本版本中(2.5.3)AutoConfigurationImportSelector实际起作用的是process()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
//去mata-inf/spring.factories文件中 查询org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的值
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
//去除重复的配置类,若我们自己写的starter 可能存主重复的
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}



SpringFactoriesLoader:去spring.factories 中去查询EnableAutoConfirution类

1
2
3
4
5
6
7
8
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

最终筛选出需要自动配置的类

image-20210817001324502

RedisAutoConfiguration

​ 导入了三个组件

​ RedisTemplate

​ StringRedisTemplate

​ JedisConnectionConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}

总结

​ SpringBoot自动配置原理:简单来说就是通过

​ @SpringBootApplication ->

​ @EnableAutoConfiguration ->

​ @Import(AutoConfigurationImportSelector.class)

​ 导入项目所有 META-INF/spring.factories 中所有org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,再通过@condition、import,@ConfigurationProperties @EnableConfigurationProperties等系列注解达到根据环境自动注入配置的目的。

具体例子可见SpringBoot04篇章