天天看點

springboot源碼解析(一)springboot是怎麼做到簡化配置的?

00 前言

慚愧地狠,前幾天的一個面試問到springboot是怎麼做到簡化配置的,我就說了個事先約定,内部實作沒有答上來。用springboot也用了一年多,從來沒想着去看看springboot是怎麼實作簡化配置,讓大家愛用這個玩意兒的。

然後搜了下,說是加載jar包下的META-INF/spring.factories檔案,但是又有個面試官問我,這裡面的配置代表了什麼意思呢?

我又瞎說了一通。

今天就找了個下資料,學習了下,然後自己點開源碼看了下,發現主脈絡寫的很清晰,并不是很難懂,就此寫一篇文章,加深下自己的記憶吧。

也為我的兩次面試哀悼。

可能我不是屬于考試型的吧。唉。

面試還是要懂些原理性的東西。

有一個面試官說的好,人有兩種能力,一種是面試時表現出的能力,一種是工作中解決實際問題的能力。

我的第一種能力太弱了,都是靠第二種能力撐着的,但是第二種能力面試時沒法面試出來,是以我每次換工作時都挺痛苦的。

廢話不多說了,開啟今天的springboot自動化配置之旅吧。

01 EnableAutoConfiguration

springboot的啟動類上有個注解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 {
           

在這其中,可以看到有個EnableAutoConfiguration,就是負責開啟我們的自動配置。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
           

打開EnableAutoConfiguration,可以看到裡面是import了AutoConfigurationImportSelector這樣一個類。

繼續追蹤這個類,AutoConfigurationImportSelector類裡主要看一個selectImports方法,這個方法的調用鍊如下:

selectImports
    ->getAutoConfigurationEntry 
        -> getCandidateConfigurations
            ->SpringFactoriesLoader.loadFactoryNames
           

SpringFactoriesLoader.loadFactoryNames方法如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}
           

可以看到斷言中說到是去META-INF/spring.factories這個檔案下去尋找有沒有自動配置類。

我們再點開SpringFactoriesLoader.loadFactoryNames這個方法确認下。

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

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryImplementationName = var9[var11];
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }

            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
        }
    }
}
           

可以看到确實調用了loadSpringFactories這個方法,loadSpringFactories方法中開頭的這段代碼

Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
           

即告訴我們,它是要找到所有META-INF/spring.factories下的檔案,然後加載進來,使用PropertiesLoaderUtils.loadProperties方法讀取其中的檔案配置。

02 spring.factories

哪些jar包下有META-INF/spring.factories檔案呢?一般是在springboot的核心類及XXX-spring-boot-autoconfigure或spring-boot-autoconfigure-XXX這樣的jar包中。

本次我們隻說EnableAutoConfiguration,打開spring-boot-autoconfigure-2.2.1.RELEASE.jar,這個就是springboot中最重要的自動配置包。

打開下面的META-INF/spring.factories 檔案,可以發現這個檔案也是也是一組一組的key=value的形式,與我們常用的properties檔案沒有什麼差別。

截取其中部分内容如下:

# 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.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
……
           

這裡EnableAutoConfiguration這個key對應的value很長,以逗号分割。

可以看到這裡的value其實都是一個個類,那麼它們是在哪裡呢?

再傳回上一層看看spring-boot-autoconfigure-2.2.1.RELEASE.jar下的org.springframework.boot.autoconfigure代碼包。

這裡,有各種各樣事先寫好的配置類。

我們思考下,為什麼在application.properties中寫上諸如spring.datasource.url=XXX的配置就能加載jdbc了?

03 application.properties

下面是我們在application.properties常用的加載jdbc的方式:

spring.datasource.url=jdbc:oracle:thin:@192.168.1.7:1521:orcl
spring.datasource.username=yaomaomao
spring.datasource.password=Iyaoshen369
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver
           

這裡配置的意思大家都應該明白,但是為什麼它能起作用呢?

我們先從spring.factories檔案中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 這個key中找到 jdbc相關的value,如下,找到了這些

……
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
……
           

打開 org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration 這個類,發現這樣的代碼

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
    public DataSourceAutoConfiguration() {
    }
           

主要看這段注解

@EnableConfigurationProperties({DataSourceProperties.class})
           

打開DataSourceProperties這個類,發現如下内容

@ConfigurationProperties(
    prefix = "spring.datasource"
)
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    private ClassLoader classLoader;
    private String name;
    private boolean generateUniqueName;
    private Class<? extends DataSource> type;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    private String jndiName;
           

至此,我們基本上是明白了@EnableAutoConfiguration是怎麼起作用的,又是怎麼與application.properties關聯的。

04 總結

最後,在面試時,我們能不能一句話總結下,springboot是怎麼做到簡化配置的?

答:主要是@EnableAutoConfiguration這個注解起的作用,這個注解是間接隐藏在springboot的啟動類注解@SpringBootApplication中。

通過這個注解,SpringApplication.run(…)的内部就會執行selectImports()方法,尋找 META-INF/spring.factories檔案,讀取裡面的檔案配置,将事先已經寫好的自動配置類有選擇地加載到Spring容器中,并且能按照約定的寫法在application.properties中配置參數或開關。

參考資料:Spring Boot面試殺手锏————自動配置原理 作者:聖鬥士Morty

繼續閱讀