1. 程式人生 > >Springboot 入口類及其實現自動配置的原理

Springboot 入口類及其實現自動配置的原理

入口類需要放在包的最外層在,能夠掃描到所有子包中的類

@SpringBootApplication註解將Application類註冊為啟動類

package com.lebron.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

簡單介紹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,第一時間收到更新內容。