我的面試(001)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 Configureorg.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:orclspring.datasource.username=yaomaomaospring.datasource.password=Iyaoshen369spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.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面試殺手鐗————自動配置原理[1] 作者:聖鬥士Morty

References

[1] Spring Boot面試殺手鐗————自動配置原理: https://blog.csdn.net/u014745069/article/details/83820511


本文分享自微信公衆號 - 架構師之殤(ysistrue)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

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