spring boot 自動配置功能解剖
說起spring boot和spring的區別,大家第一反應就是spring boot 少了很多配置,但不是說少了很多配置很多功能就沒有了,或者比spring就少了很多功能,而是spring boot 自己約定了很多預設配置,讓大家感覺不到其中的一些資訊
問題來了,spring boot的自動配置怎麼實現的?今天我將自己這2天學習到的東西分享一下,有不對的地方可以說出來一起討論
首先我們先看一下下面這個註解:
@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 {}; }
看到@import那個註解了嗎?這個註解就是會把第三方jar的類載入到當前spring容器,接下來我們看一下import的這個類,原始碼如下
@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)).booleanValue() : true; } }
下面是父類AutoConfigurationImportSelector 中關鍵2個方法的原始碼
public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { //判斷 enableautoconfiguration註解有沒有開啟,預設開啟 return NO_IMPORTS; } else { try { //第一部分 :獲取 META-INF/spring-autoconfigure-metadata.properties 的配置資料 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); AnnotationAttributes attributes = this.getAttributes(annotationMetadata); //第二部分 :獲取 META-INF/spring.factoies 的類相關資料 List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); //去重 configurations = this.removeDuplicates(configurations);、 //排序 configurations = this.sort(configurations, autoConfigurationMetadata); //第三部分:去除一些多餘的類,根據EnableAutoConfiguration 註解的一個exclusions屬性 Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); //第四部:根據OnClassCondition註解過濾調一些條件沒有滿足的 configurations = this.filter(configurations, autoConfigurationMetadata); //第五部:廣播事件 this.fireAutoConfigurationImportEvents(configurations, exclusions); return (String[])configurations.toArray(new String[configurations.size()]); } catch (IOException var6) { throw new IllegalStateException(var6); } } } protected boolean isEnabled(AnnotationMetadata metadata) { return true; }
首先程式是先呼叫EnableAutoConfigurationImportSelector 的selectImport方法,也就是父類的這個方法,下面我們先研究一下程式碼
大家看上面原始碼的第一部分,獲取spring-autoconfigure-metadata.properties的程式碼,深究進去的程式碼如下
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
}
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while(urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource((URL)urls.nextElement())));
}
return loadMetadata(properties);
} catch (IOException var4) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);
}
}
ClassLoader.getResource()方法找到具有給定名稱的資源。資源是一些資料(影象,音訊,文字等),返回URL物件讀取資源。
該方法就是獲取該目錄下的配置資料
第二部分:道理跟第一部分一樣獲取相關類的資料
第三步就是去除一些不用的class,這是具體過濾的程式碼
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
String[] candidates = (String[])configurations.toArray(new String[configurations.size()]);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
Iterator var8 = this.getAutoConfigurationImportFilters().iterator();
while(var8.hasNext()) {
AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
this.invokeAwareMethods(filter);
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for(int i = 0; i < match.length; ++i) {
if (!match[i]) {
skip[i] = true;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
} else {
List<String> result = new ArrayList(candidates.length);
int numberFiltered;
for(numberFiltered = 0; numberFiltered < candidates.length; ++numberFiltered) {
if (!skip[numberFiltered]) {
result.add(candidates[numberFiltered]);
}
}
if (logger.isTraceEnabled()) {
numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return new ArrayList(result);
}
}
AutoConfigurationImportFilter 是一個介面,OnClassCondition才是它的實現類,主要功能就是第二部載入的類中不是所有都是要載入的,spring boot 提供了很多條件註解,具體如下
@ConditionalOnClass : classpath中存在該類時起效
@ConditionalOnMissingClass : classpath中不存在該類時起效
@ConditionalOnBean : DI容器中存在該型別Bean時起效
@ConditionalOnMissingBean : DI容器中不存在該型別Bean時起效
@ConditionalOnSingleCandidate : DI容器中該型別Bean只有一個或@Primary的只有一個時起效
@ConditionalOnExpression : SpEL表示式結果為true時
@ConditionalOnProperty : 引數設定或者值一致時起效
@ConditionalOnResource : 指定的檔案存在時起效
@ConditionalOnJndi : 指定的JNDI存在時起效
@ConditionalOnJava : 指定的Java版本存在時起效
@ConditionalOnWebApplication : Web應用環境下起效
@ConditionalOnNotWebApplication : 非Web應用環境下起效
以上註解都是元註解@Conditional
演變而來的,過濾調一些沒有滿足條件的class
第五步:廣播事件
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
//獲取監聽者
List<AutoConfigurationImportListener> listeners = this.getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
//產生相應的事件(自動載入類,配置資料和不滿足條件的類)
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
Iterator var5 = listeners.iterator();
while(var5.hasNext()) {
AutoConfigurationImportListener listener = (AutoConfigurationImportListener)var5.next();
this.invokeAwareMethods(listener);
//觸發事件
listener.onAutoConfigurationImportEvent(event);
}
}
}
關鍵的一個問題來了,回到源頭,專案啟動之後什麼時候會去執行AutoConfigurationImportSelector的selectImports方法?
查了一下資料,其實spring boot是從我們SpringApplication.run方法開始最終執行最終執行到selectImports方法,然後將selectImports方法得到的資料注入到容器裡面
springApplication.run----->refreshContext()----->AbstractApplicationContext.refresh---->PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors----->ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry----------->ConfigurationClassPostProcessor.processConfigBeanDefinitions------>ConfigurationClassParser.parse
最終到如下程式碼
private void processDeferredImportSelectors() {
List<ConfigurationClassParser.DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
Iterator var2 = deferredImports.iterator();
while(var2.hasNext()) {
ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var2.next();
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
//執行匯入自動化資料配置
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
//處理這些資料,注入容器
this.processImports(configClass, this.asSourceClass(configClass), this.asSourceClasses(imports), false);
} catch (BeanDefinitionStoreException var6) {
throw var6;
} catch (Throwable var7) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", var7);
}
}
}
好了,spring boot的自動化注入大致講解完了,
現在又有一個問題了,如果自己要做一個類似於第三方的jar,讓當前的容器載入我這個第三方的bean怎麼做呢?其實也是非常簡單。
第一步:肯定是新建一個spring boot專案A
第二步:定義類
public class people{
}
@Configuration
public class MyConfig {
@Bean
public People people (){
return new People();
}
}
第三步:將A的jar放到B中
第四部:在B中寫如下測試程式碼,並啟動
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context =SpringApplication.run(Application.class,args);
People people = context.getBean(People .class);
System.out.println(people );
}
}
不過這樣啟動會報錯,找不到這個類,那是因為你的第五步沒有做
第五步:在core-bean專案resource下新建資料夾META-INF,在資料夾下面新建spring.factories檔案,檔案中配置,key為自定配置類EnableAutoConfiguration的全路徑,value是配置類的全路徑(就上面分析spring boot自動配置的那個spring.factories檔案一樣的道理)
這樣之後就可以實現了
到此為止大部分該講的也都講完了,如果有什麼不對的地方,可以提出來討論