源碼學習系列之SpringBoot自動配置(篇一)
- 2019 年 11 月 2 日
- 筆記
源碼學習系列之SpringBoot自動配置源碼學習(篇一)
ok,本部落格嘗試跟一下Springboot的自動配置源碼,做一下筆記記錄,自動配置是Springboot的一個很關鍵的特性,也容易被忽略的屬性,因為這個屬性被包括在@SpringBootApplication
註解里,所以不去跟一下源碼都不知道還有這個屬性,ps:本部落格源碼基於SpringBoot1.5.7版本
@SpringBootApplication
ok,跟一下@SpringBootApplication
,發現@SpringBootApplication
其實是一個複合的註解,由很多註解構成,@EnableAutoConfiguration
其實只是其一部分,@EnableAutoConfiguration
就是開啟自動配置的註解
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.core.annotation.AliasFor; @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 { .... Class<?>[] scanBasePackageClasses() default {}; }
@EnableAutoConfiguration
點進@EnableAutoConfiguration
,比較重要的列出來:
- @AutoConfigurationPackage
- @Import({EnableAutoConfigurationImportSelector.class})
package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.Import; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
@AutoConfigurationPackage
先看@AutoConfigurationPackage
源碼,這個註解是開啟自動配置包的,關注點在@Import({Registrar.class})
,核心在Registrar類
備註:@import註解是Spring的底層註解,作用是導入一個組件到容器里
package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar; import org.springframework.context.annotation.Import; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import({Registrar.class}) public @interface AutoConfigurationPackage { }
Registrar 類是AutoConfigurationPackages類的靜態內部類,
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { Registrar() { } public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()); } public Set<Object> determineImports(AnnotationMetadata metadata) { return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata)); } }
看一下(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()
獲取的是什麼,用idea的工具,計算表達式,可以看到其實獲取的是SpringBoot啟動類上面的包名
AnnotationMetadata:SpringBoot註解元數據
所以,@AutoConfigurationPackage開啟後,就可以將主配置類(@SpringBootApplication)所在包及其子包裡面的所有組件都掃描到Spring容器里
public static void register(BeanDefinitionRegistry registry, String... packageNames) { if (registry.containsBeanDefinition(BEAN)) { BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues(); constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames)); } else {// Spring容器里沒有找到對應組件 /* 將組件註冊到Spring容器里 */ GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class); beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); beanDefinition.setRole(2); registry.registerBeanDefinition(BEAN, beanDefinition); } }
ok,跟了源碼,當然只是簡單跟一下,沒有特別細的跟,這個過程可以看出這裡的組件掃描只是掃描主配置類(@SpringBootApplication)所在包及其子包裡面的所有組件,所以,寫了例子驗證一下:
在Application類包外寫個Controller測試類
@RestController public class TestController { @GetMapping("/hello") public String hello(){ return "hello world!"; } }
啟動項目,進行訪問,發現都是404找不到這個介面,再將這個Controller放在包裡面,才可以掃描到
@Import({EnableAutoConfigurationImportSelector.class})
package org.springframework.boot.autoconfigure; import org.springframework.core.type.AnnotationMetadata; /** @deprecated */ @Deprecated public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector { public EnableAutoConfigurationImportSelector() { } protected boolean isEnabled(AnnotationMetadata metadata) { return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true; } }
主要看一下其基類AutoConfigurationImportSelector程式碼,看一下selectImport方法:
/**裝載很多自動配置類,以全類名的方式返回一個字元數組**/ public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { try { //獲取自動配置的元數據 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); //裝載配置屬性 AnnotationAttributes attributes = this.getAttributes(annotationMetadata); //通過類載入器讀取所有的配置類全類名 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); configurations = this.removeDuplicates(configurations); configurations = this.sort(configurations, autoConfigurationMetadata); Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = this.filter(configurations, autoConfigurationMetadata); this.fireAutoConfigurationImportEvents(configurations, exclusions); return (String[])configurations.toArray(new String[configurations.size()]); } catch (IOException var6) { throw new IllegalStateException(var6); } } }
- AutoConfigurationMetadata資訊,獲取整個JavaEE體系的一些配置類,當然是Springboot集成的,比如有WebMvcAutoConfiguration自動配置類
跟一下getCandidateConfigurations方法,SpringFactoriesLoader是Spring-code工程的工廠載入類,使用SpringFactoriesLoader,需要在模組的META-INF/spring.factories文件自己配置屬性,這個Properties格式的文件中的key是介面、註解、或抽象類的全名,value是以逗號 「 , 「 分隔的實現類,使用SpringFactoriesLoader可以實現將相應的實現類注入Spirng容器
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; }
loadFactoryNames方法程式碼,
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; /* 將spring.factories的類都裝載到Spring容器*/ public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { //將META-INF/spring.factories文件里配置的屬性都裝載到Enumeration數據結構里 Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); ArrayList result = new ArrayList(); //遍歷獲取屬性,然後再獲取對應的配置類全類名 while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } catch (IOException var8) { throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8); } }
然後META-INF/spring.factories文件放在哪?很顯然是放在Springboot的自動配置模組里,如圖:
所以,@Import({EnableAutoConfigurationImportSelector.class})開啟之後,主要是EnableAutoConfigurationImportSelector這個類的作用就是在SpringBoot啟動時候將從SpringBoot自動配置工程的META-INF/spring.factories文件中獲取指定的值,經過SpringFactoriesLoader載入之後將很多自動配置類載入到Spring容器,所以我們不需要配置,mvc等等默認配置就已經隨著SpringBoot啟動而自動生效
ok,Springboot的自動配置類都在這個包里,源碼很多,所以本部落格只是簡單跟一下源碼