首先對於一個SpringBoot工程來講,最明顯的標誌的就是
@SpringBootApplication
它標記了這是一個SpringBoot工程,因此今天的 SpringBoot自動裝配原理也就是從它開始提及。spring
首先咱們來看下@SpringBootApplication 這個註解的背後又有什麼玄機呢,咱們按下 ctrl + 鼠標左鍵,輕輕的點一下,此時見證奇蹟的時刻..
咱們看到以下優雅的代碼:
這其中有兩個比較容易引發咱們注意的地方,一個是@SpringBootConfiguration
註解,另外一個是 @EnableAutoConfiguration
註解;之因此說這個兩個註解比較吸引咱們的眼球, 不是由於它們長大的好看,而是由於其餘的註解太難看了(主要是由於其餘的註解咱們都是比較熟悉,即便不知道他們是幹什麼的,能夠確定更自動裝配是沒有關係的)。 而後咱們又伸出了邪惡的小手,開啓了熟悉的操做,按下了Ctrt + 鼠標左鍵,瞪着色咪咪的小眼睛,瞳孔放大了百倍等待着奇蹟的出現... 擦... 擦...擦...
什麼也沒有...
那我要你有何用,這麼頂級的世界級的開源項目,怎麼會讓一個沒用的傢伙存在呢? 因而動用了上億的腦細胞大軍,通過複雜的運算,得出了一個不靠譜的結論:它可能使用來標記這是一個SpringBoot工程的配置。由於SpringBootConfiguration
翻譯過來就是SpringBoot的配置,因而心中又是幾萬只羊駝在萬馬奔騰,大漠飛揚。緩存
氣定神閒以後,秉承着·失敗是成功之母"的信念, 熟練的左手行雲流水般的按下了 Ctrl + Table 鍵,回到了最初的的地方。眼睛盯着 @EnableAutoConfiguration
,環顧左右,在地址欄輸入了谷歌翻譯, 結果顯示 自動裝配。我找的就是你,真是衆裏尋他千百度,那人卻在燈火闌珊處。 熟練的按下了 Ctrl +左鍵,火燒眉毛的想要進入; 內心默默背誦起了《桃花源記》的經典詩句 ∶ide
林盡水源,便得一山,山有小口,彷彿如有光。便舍船,從口入。初極狹,才通人。復行數十步,豁然開朗
此時此刻心情愉悅,有過前面的經歷以後,在面對新的世界時候,咱們淡定了許多。 此時大腦高速運轉,沒有再糾結,直搗黃龍,進入了AutoConfigurationImportSelector.class
類,由於谷歌翻譯告訴咱們,這個是自動配置導入選擇器。 因而咱們發現了—片新天地spring-boot
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } // 獲取自動配置的實體 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } // 具體用來加載自動配置類得方法 protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); // 獲取候選的配置類,即便後宮佳麗三千,也是要篩選的 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 根據狀況,自動配置須要的配置類和不須要的配置了 configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, ); configurations.removeAll(exclusions); configurations = getConfigurationClassFilter().filter(configurations); fireAutoConfigurationImportEvents(configurations, exclusions); // 返回最終須要的配置 return new AutoConfigurationEntry(configurations, exclusions); } }
而這個自動配置的實體 AutoConfigurationEntry
裏面有兩個屬性,configurations
和 exclusions
。this
protected static class AutoConfigurationEntry { // 用來存儲須要的配置項 private final List<String> configurations; // 用來存儲排除的配置項 private final Set<String> exclusions; private AutoConfigurationEntry() { this.configurations = Collections.emptyList(); this.exclusions = Collections.emptySet(); } }
在後面能夠看到 getAutoConfigurationEntry()
方法返回了一個對象 return new AutoConfigurationEntry(configurations, exclusions);
這裏也就是把咱們須要的配置都拿到了。url
那他是怎麼拿到的候選的配置類呢? 咱們接着看這個獲取候選配置類的方法 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
進到方法後咱們看到下面這個方法具體獲取候選配置類的方法內容
翻譯
這裏咱們跟着斷點去走,首先進入getSpringFactoriesLoaderFactoryClass()
方法3d
protected Class<?> getSpringFactoriesLoaderFactoryClass() { // 返回的是EnableAutoConfiguration字節碼對象 return EnableAutoConfiguration.class; }
接着咱們在進入getBeanClassLoader()
方法,這裏就是一個類加載器code
protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; }
最後咱們在進入loadFactoryNames()
方法,這個方法就是根據剛纔的字節碼文件和類加載器來找到候選的配置類。傳遞過來的字節碼對象
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } // 獲取的EnableAutoConfiguration.class的權限定名 //org.springframework.boot.autoconfigure.EnableAutoConfiguration String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
以下圖:
最後經過loadSpringFactories()
來獲取到全部的配置類
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { // 緩存加載的配置類 Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try { // 去資源目錄下找 Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); // 加載完成放到緩存中 cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } // 返回加載到的配置類 return result; }
這裏咱們要看下怎麼從資源目錄下 FACTORIES_RESOURCE_LOCATION 加載的。下面是加載配置文件的路徑:
也就是項目啓動的時候會去加載全部 META-INF 下的全部的 spring.factories 文件,咱們搜一下這個這個文件,我搭建的是一個很簡單的 SpringBoot 工程,它會去這幾個 jar 裏面找相關的配置類
可是最後自動裝配的類是這個spring-boot-autoconfigure-2.4.3.RELEASE.jar
而根據EnabLeAutoConfiguration.class
字節碼加載的配置類就只有這118自動配置類
小結 實際上SpringBoot的自動裝配原理,其實就是在項目啓動的時候去加載META-INF下的 spring.factories 文件,好像也沒有那麼高大上。固然在啓動的過程當中還會有其餘的配置項的加載,這裏咱麼直說了自動裝配的加載過程。但願對你們能夠有所啓發。 問題∶ 明白了SpringBoot的自動裝配原理, 若是咱們須要讓項目啓動的時候就加載咱們自定義的配置類, 該如何寫呢?