Spring Boot自动配置的原理

简单介绍Spring Boot

首先先给大家简单介绍一下Spring Boot

直接上Spring Boot官方文档的介绍
- Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
- We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration.

简单翻译一下,Spring Boot能让我们快速的搭建一个可以独立运行、准生产级别的基于Spring框架的项目,并且只需要使用很少的Spring配置。

Spring Boot于2013年发布第一个版本,到现在已经有五年的时间了。有很多公司都在使用Spring Boot,同样也有很多开发者开始使用并了解Spring Boot。

跟源码,看原理

使用过Spring Boot的开发者对于@SpringBootApplication注解肯定不陌生,@SpringBootApplication注解用在启动类上。下面我们来看一下@SpringBootApplication源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...
}

可以看出SpringBootApplication注解是一个组合注解,主要组合了@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan。

@SpringBootConfiguration注解作用是把启动类声明成一个配置类,
@ComponentScan注解作用是为@Configuration注解的类配置组件扫描指令。

我们需要关注的是@EnableAutoConfiguration注解,下面看一下@EnableAutoConfiguration注解的源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}

可以看到的是@EnableAutoConfiguration注解导入了AutoConfigurationImportSelector类,下面我们再跟进AutoConfigurationImportSelector类看一下究竟
我们在AutoConfigurationImportSelector类中注意到getCandidateConfigurations方法

public class AutoConfigurationImportSelector
        implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
        BeanFactoryAware, EnvironmentAware, Ordered {
     ...
    /**
     * Return the auto-configuration class names that should be considered. By default
     * this method will load candidates using {@link SpringFactoriesLoader} with
     * {@link #getSpringFactoriesLoaderFactoryClass()}.
     * @param metadata the source metadata
     * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
     * attributes}
     * @return a list of candidate configurations
     */
    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }
    ...
}

通过方法注释可以很清楚的知道这个方法用于返回一些需要考虑自动配置的类名,那么我们可以再跟进SpringFactoriesLoader.loadFactoryNames方法看一下具体返回哪些虑自动配置的类名

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null)
        return result;
    try {
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                List<String> factoryClassNames = Arrays.asList(
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                result.addAll((String) entry.getKey(), factoryClassNames);
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

上面那一块代码,我们需要关注的是这一段

Enumeration<URL> urls = (classLoader != null ?
    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

从这一段代码我们可以知道,Spring Boot会去读取jar包路径下的FACTORIES_RESOURCE_LOCATION文件中的内容,我们继续从FACTORIES_RESOURCE_LOCATION点进去

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

我们得出一个结论,Spring Boot会去加载jar包中META-INF路径下的pring.factories文件中的内容。

正好spring-boot-autoconfigure.jar内就有一个spring.factories文件,在此文件中声明了一些自动配置的类名,用”,”进行分隔,用”\”进行换行

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
...

Spring Boot会读取到spring.factories文件中这些EnableAutoConfiguration的类,然后自动配置到Spring容器中。

下面我们再跟着RedisAutoConfiguration类进去看一下

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        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;
    }

}

RedisAutoConfiguratio类上@ConditionalOnClass(RedisOperations.class)注解的作用是,在当类路径下有RedisOperations的条件下才会注册RedisAutoConfiguratio类,所以我们想要自动注册Redis,就需要在pom文件中引入spring-boot-starter-data-redis包,从而使得类路径下有RedisOperations类。

RedisAutoConfiguration类上的@EnableConfigurationProperties(RedisProperties.class)引入了RedisProperties配置类,我们再跟进去看一下

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {

    private int database = 0;
    private String url;
    private String host = "localhost";
    private String password;
    private int port = 6379;
    private boolean ssl;
    private Duration timeout;
    private Sentinel sentinel;
    private Cluster cluster;
    ...
}

这个配置类告诉我们,如果我们需要自动配置Redis,在配置文件application.properties中添加的配置需要以spring.redis为前缀,可配置的参数都在这个配置类里面。比如下面这样的配置:

spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=7000

所以我们想要实现Redis的自动配置,只需要在pom文件中引入依赖,以及在配置文件中加上上面的这些配置就行了,RedisAutoConfiguration会自动为我们注册RedisTemplate以及StringRedisTemplate。

同样的,其他功能的一些自动配置原理也都是如此,基于jar包META-INF路径下的pring.factories文件中的一些自动配置类去实现自动配置。

总结

今天这篇文章可能跟源码跟的比较多,也贴了很多代码,很多人可能会看不下去,那么看不下去源码的就来看一下总结好了。

  • Spring Boot启动类上的@SpringBootApplication注解会去加载所有jar包META-INF路径下的spring.factories文件
  • spring-boot-autoconfigure.jar中的spring.factories文件中有一些自动配置的类名,会被Spring Boot加载到,实现自动配置
  • 在spring.factories中加载到的这些自动配置类,会有一些判断是否引入依赖包等的条件注解来判断是否继续加载这个配置类。也会导入配置类来读取配置文件中相应的配置

Spring Boot官方为我们提供了很多第三方工具的自动配置依赖,极大的缩短了我们搭建项目花费的时间。

如果我们想要实现的自动配置功能Spring Boot官方没有为我们提供实现,我们想要自己去实现也很简单,只需要自己写好配置类,然后在jar包中创建META-INF目录和spring.factories文件,引入自定义的配置类名就好了。

喜欢这篇文章的朋友,欢迎长按下图关注公众号lebronchen,第一时间收到更新内容。
扫码关注

猜你喜欢

转载自blog.csdn.net/Leon_cx/article/details/81487512