剛作後端開發的時候,最先接觸的是基礎的spring,爲了引用二方包提供bean,還須要在xml中增長對應的包<context:component-scan base-package="xxx" />
或者增長註解@ComponentScan({ "xxx"})
。當時以爲挺urgly的,但也沒有去研究有沒有更好的方式。git
直到接觸Spring Boot 後,發現其能夠自動引入二方包的bean。不過一直沒有看這塊的實現原理。直到最近面試的時候被問到。因此就看了下實現邏輯。github
講原理前先說下使用姿式。面試
在project A中定義一個bean。spring
package com.wangzhi; import org.springframework.stereotype.Service; @Service public class Dog { }
並在該project的resources/META-INF/
下建立一個叫spring.factories
的文件,該文件內容以下後端
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog
而後在project B中引用project A的jar包。springboot
projectA代碼以下:併發
package com.wangzhi.springbootdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; @EnableAutoConfiguration public class SpringBootDemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args); System.out.println(context.getBean(com.wangzhi.Dog.class)); } }
打印結果:分佈式
com.wangzhi.Dog@3148f668
整體分爲兩個部分:一是收集全部spring.factories
中EnableAutoConfiguration
相關bean的類,二是將獲得的類註冊到spring容器中。高併發
在spring容器啓動時,會調用到AutoConfigurationImportSelector#getAutoConfigurationEntry
this
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } // EnableAutoConfiguration註解的屬性:exclude,excludeName等 AnnotationAttributes attributes = getAttributes(annotationMetadata); // 獲得全部的Configurations List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); // 去重 configurations = removeDuplicates(configurations); // 刪除掉exclude中指定的類 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); }
getCandidateConfigurations
會調用到方法loadFactoryNames
:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { // factoryClassName爲org.springframework.boot.autoconfigure.EnableAutoConfiguration String factoryClassName = factoryClass.getName(); // 該方法返回的是全部spring.factories文件中key爲org.springframework.boot.autoconfigure.EnableAutoConfiguration的類路徑 return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // 找到全部的"META-INF/spring.factories" Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); // 讀取文件內容,properties相似於HashMap,包含了屬性的key和value Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); // 屬性文件中能夠用','分割多個value for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
在上面的流程中獲得了全部在spring.factories
中指定的bean的類路徑,在processGroupImports
方法中會以處理@import註解同樣的邏輯將其導入進容器。
public void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { // getImports即上面獲得的全部類路徑的封裝 grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get( entry.getMetadata()); try { // 和處理@Import註解同樣 processImports(configurationClass, asSourceClass(configurationClass), asSourceClasses(entry.getImportClassName()), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); } } private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) { ... // 遍歷收集到的類路徑 for (SourceClass candidate : importCandidates) { ... //若是candidate是ImportSelector或ImportBeanDefinitionRegistrar類型其處理邏輯會不同,這裏不關注 // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); // 看成 @Configuration 處理 processConfigurationClass(candidate.asConfigClass(configClass)); ... } ... }
能夠看到,在第一步收集的bean類定義,最終會被以Configuration
同樣的處理方式註冊到容器中。
@EnableAutoConfiguration
註解簡化了導入了二方包bean的成本。提供一個二方包給其餘應用使用,只須要在二方包裏將對外暴露的bean定義在spring.factories
中就行了。對於不須要的bean,能夠在使用方用@EnableAutoConfiguration
的exclude
屬性進行排除。
本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G,須要本身領取。
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q