SpringBoot自動裝配原理分析之中篇

1. @SpringBootApplication

1.1 @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 {

}

@Inherited註解聲明註解上時,在類上表明該類具有繼承性。子類會自動繼承該註解。
需要我們注重關注三個註解:@SpringBootConfiguration@ComponentScan@EnableAutoConfiguration

1.1.1 @SpringBootConfiguration
1.1.2 @ComponentScan
1.1.3 @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,看到這裏如果看過前一篇
SpringBoot自動裝配原理分析之上篇我想你應該知道AutoConfigurationImportSelector必然是ImportSelectorImportBeanDefinitionRegistrar的實現類。

2 AutoConfigurationImportSelector

我們可以看到一些信息:

  • AutoConfigurationImportSelector它實現了DeferredImportSelector接口,而DeferredImportSelector接口繼承自ImportSelector接口;
  • 實現了Ordered接口定義加載順序的接口;
//從定義上可以看到實現了DeferredImportSelector接口
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
	//....略
}

2.1 解讀方法#selectImports

小夥伴兒,我們回到AutoConfigurationImportSelector類中,又要準備深入了,雞凍不…哈哈
看看方法名,selectImports選擇導入,導入什麼,顯然就是要自動裝配的類。怎麼還要選擇性的導入自動裝配呢?我們想想是不是要過濾一些,排除一些多餘的呢?這個猜想不錯,我們看看就明白究竟了。

//入參是註解屬性元信息
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //獲取自動裝配候選名單
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //去除重複
            configurations = this.removeDuplicates(configurations);
            //去除自動裝配排除的名單列表
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            //檢查排除類名單是否合法
            this.checkExcludedClasses(configurations, exclusions);
            //從候選名單列表中移除需要排除的名單列表
            configurations.removeAll(exclusions);
            //過濾自動裝配組件
            configurations = this.filter(configurations, autoConfigurationMetadata);
            //自動裝配事件
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }

小夥伴兒們,又需要我們逐一層層深入了。記住,只有深入了,你纔會體驗到前所未有的爽,那感覺終生難忘,回味無窮,嘿嘿,,,,看源碼是痛苦的,但看明白後就有種醍醐灌頂,豁然開朗的感覺。

2.2 獲取候選裝配組件

解讀方法getCandidateConfigurations從命名上可以知道Candidate是候選的意思,大概就是獲取候選配置類信息

public class AutoConfigurationImportSelector{
 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
 //加載在 `META-INF/spring.factories` 裏的類名的數組,他們作爲自動裝配的候選類名單
        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;
    }
	}
}

我們繼續跟進SpringFactoriesLoader.loadFactoryNames這個方法看看

public final class SpringFactoriesLoader {
	//配置文件路徑
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
 	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        //此處又調用了該類的私有方法#loadSpringFactories()
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    
//小夥伴兒們我們繼續深入....有多深都要進去,只是進去看看
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		//MultiValueMap就是個Map,因爲返回就是Map
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
		//加載指定路徑下文件內容
		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();
			//轉爲Resource
			UrlResource resource = new UrlResource(url);
			//加載爲Peroperties文件進行讀取
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			Iterator var6 = properties.entrySet().iterator();

			while(var6.hasNext()) {
				Entry<?, ?> entry = (Entry)var6.next();
				//我們打開META-INF/spring.factories文件看看,
				//可以發現文件key是接口的全類名,value是實現類的全類名列表,代碼自然就懂了
				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;         
    }

}

說明SpringFactoriesLoader是Spring的工廠機制加載器,看完#loadSpringFactories方法,明白原理如下:

  • 加載指定ClassLoader下META-INF/spring.factories文件資源內容
  • 轉爲Properties文件進行讀取,放入Map中,key爲接口的全類名,value爲實現類的全類名列表,然後進行返回。

找到maven依賴的自動裝配模塊org.springframework.boot:spring-boot-autoconfigure,META-INF/spring.factories文件下,以EnableAutoConfiguration全類名爲Key的配置如下所示:

# 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,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
//其他略.......

2.3 排除自動裝配組件

接下來,執行this.getExclusions(annotationMetadata, attributes) 方法,去排除一些不需要的組件。
這樣說你可能還不清楚,比如在@EnableAutoConfiguration註解定義中可以看到

public @interface SpringBootApplication{
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

有兩個屬性分別是excludeexcludeName,在實際項目中你可能也這樣寫過,配置多數據源時,需要我們排除默認的數據源,使用我們自己配置的數據源,配置如下:

@SpringBootApplication(exclude={
	DataSourceAutoConfiguration.class,
	DataSourceTransactionManagerAutoConfiguration.class})

這就是要告訴自動裝配需要排除的組件類了。

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        Set<String> excluded = new LinkedHashSet();
        //@EnableAutoConfiguration註解中exclude和excludeName用來添加排除的組件
        excluded.addAll(this.asList(attributes, "exclude"));
        excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
        excluded.addAll(this.getExcludeAutoConfigurationsProperty());
        return excluded;
    }

將需要排除的類放在集合中,然後返回,之後檢查排除的集合類是否合法最後進行移除。

2.4 過濾自動裝配組件

繼續往下看了,調用configurations = this.filter(configurations, autoConfigurationMetadata); 方法進行過濾。

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
        long startTime = System.nanoTime();
        String[] candidates = StringUtils.toStringArray(configurations);
        boolean[] skip = new boolean[candidates.length];
        boolean skipped = false;
        //主要方法getAutoConfigurationImportFilters()
        Iterator var8 = this.getAutoConfigurationImportFilters().iterator();

        while(var8.hasNext()) {
        //可以看出迭代的類是AutoConfigurationImportFilter的子類!!!!
            AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
            this.invokeAwareMethods(filter);
            boolean[] match = filter.match(candidates, autoConfigurationMetadata);
		//爲節省篇幅,其他代碼省略....
            
        }

        
    }
    
//#filter()方法調用
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
        return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
    }
    

返回進入SpringFactoriesLoader

public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryClass, "'factoryClass' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
		
		return result;
	}

可以看出AutoConfigurationImportSelector#filter中的AutoConfigurationImportFilter 對象集合是有SpringFactoriesLoader 加載的。找到maven依賴的自動裝配模塊org.springframework.boot:spring-boot-autoconfigure,META-INF/spring.factories文件下

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition

我們可以看到,SpringFactoriesLoader#loadFactories方法與我們前面看獲取候選自動裝配方法時調用的SpringFactoriesLoader#loadFactoryNames 差別是後者是靜態方法,前者是調用後者的。點開OnClassCondition 可以發現它是AutoConfigurationImportFilter實現類。可以說明AutoConfigurationImportSelector#filter方法的作用是過濾META-INF/spring.factories 資源中那些當前ClassLoader不存在的類。
SpringBoot內部提供了特有的註解:條件註解(Conditional Annotation)。比如@ConditionalOnBean、@ConditionalOnClass、@ConditionalOnExpression、@ConditionalOnMissingBean等。@ConditionalOnClass用來判斷需要依賴自動裝配的class必需被ClassLoader提前裝載,然後解析其註解元信息,從而跟據依賴是否存在來判斷是否裝載。

2.4 自動裝配事件

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);
            }
        }
    }

AutoConfigurationImportListener用來監聽AutoConfigurationImportEvent

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