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,第一時間收到更新內容。
掃碼關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章