SpringBoot自动配置详解

举例:Redis自动配置

在读SpringCloud代码的过程中,经常会看到和自动配置相关的内容,这里插入一篇关于自动配置过程的分析,希望解决阅读SpringCldou源码时的一些障碍。这里用Redis的自动配置进行说明,其他的情况都是类似的。

客户端从Redis获取数据

  • 首先,启动本地的redis: ./src/redis-server redis.conf, 端口6379
  • 使用客户端连接,并使用keys *查看内容
    1
    2
    3
    redis 127.0.0.1:6379> keys *
    1) "ttlMap"
    2) "test1"

SpringBoot获取数据

  • 创建service, 直接注入StringRedisTemplate

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Service
    public class RedisExample {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    public Set<String> keys() {
    return stringRedisTemplate.keys("*");
    }
    }
  • 创建Test类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class SpringbootRedisDemoApplicationTests {
    @Autowired
    RedisExample redisExample;
    @Test
    public void testRedis() {
    redisExample.keys().forEach(key->System.out.println(key));
    }
    }
  • 执行结果, 打印出

    1
    2
    ttlMap
    test1

从上面的步骤可以看出,没有任何地方配置读取Redis客户端、Server、Port等信息,那么StringRedisTemplate如何知道怎么访问Redis呢?如果在application.properties中直接配置spring.redis.host, spring.redis.port等信息就会生效。StringRedisTemplate是如何读取配置的呢?答案就是“自动配置”。

SpringBootApplication注解

SpringBoot的启动类上会配置@SpringBootApplication,其定义如下:

1
2
3
4
5
6
7
8
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public @interface SpringBootApplication {

SpringBootApplication上配置了很多annotation,@Target, @Retention都是比较常规的,对此不熟悉的同学可以提前阅读扩展资料1
和自动配置相关的配置是@EnableAutoConfiguration

EnableAutoConfiguration注解

1
2
3
4
5
6
7
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  • @Import能够导入Configuration配置类,也能够导入实现了ImportSelector接口的类。ConfigurationClassParser会通过Overvide其selectImports方法来返回的String数组来加载配置类。
  • EnableAutoConfigurationImportSelector中的selectImports方法中获取configurations。List<String> configurations = getCandidateConfigurations(metadata,attributes);
  • getCandidateConfigurations方法获取configurations的方法是:`List configurations = SpringFactoriesLoader.loadFactoryNames(
    getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());`
    

SpringFactoriesLoader.loadFactoryNames

该方法获取类路径下spring.factories下key为EnableAutoConfiguration全限定名对应值。

打开spring-boot-autoconfigure.jar中META-INF/maven/spring.factories文件,可以看到如下结果:

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
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
...

会加载上面所有的配置类,下面选择RedisAutoConfiguration进行说明

RedisAutoConfiguration

该类上的注解如下:

1
2
3
4
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {

其中,

  • @Configuration:标识此类为一个bean, 可以导入容器
  • @ConditionalOnClass:表示对应的类在classpath下存在时,才会去解析对应的配置文件。也就是说,如果classpath中JedisConnection.class中不存在时,就不会自动配置了。
  • @EnableConfigurationProperties:开启对@ConfigurationProperties的支持,@ConfigurationProperties可以绑定properties文件中的属性了。此例中RedisProperties.class定义如下:
    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
    @ConfigurationProperties(prefix = "spring.redis")
    public class RedisProperties {
    /**
    * Database index used by the connection factory.
    */
    private int database = 0;
    /**
    * Redis server host.
    */
    private String host = "localhost";
    /**
    * Login password of the redis server.
    */
    private String password;
    /**
    * Redis server port.
    */
    private int port = 6379;
    /**
    * Connection timeout in milliseconds.
    */
    private int timeout;
    ......

RedisProperties将会被注册为Bean,并且可以用prefix.属性覆盖默认值。

  • redisConnectionFactory
    @ConditionalOnMissingBean会启用一个Bean定义,但必须是这个Bean之前未定义过才有效。这里导入了JedisConnectionFactory

    1
    2
    3
    4
    5
    6
    @Bean
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    public JedisConnectionFactory redisConnectionFactory()
    throws UnknownHostException {
    return applyProperties(createJedisConnectionFactory());
    }
  • RedisConfiguration
    导入了RedisConfiguration,这里使用了上一步导入容器的redisConnectionFactoryRedisConfiguration中定义了RedisTemplateStringRedisTemplate,完成了自动加载。所以在Service中可以直接使用StringRedisTemplate

    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
    /**
    + Standard Redis configuration.
    */
    @Configuration
    protected static class RedisConfiguration {
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
    RedisConnectionFactory redisConnectionFactory)
    throws UnknownHostException {
    RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
    }
    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
    RedisConnectionFactory redisConnectionFactory)
    throws UnknownHostException {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
    }
    }

扩展资料

  1. Spring注解原理的详细剖析与实现
  2. 在Spring Boot中使用 @ConfigurationProperties 注解