您好,登錄后才能下訂單哦!
剛做后端開發的時候,最早接觸的是基礎的spring,為了引用二方包提供bean,還需要在xml中增加對應的包<context:component-scan base-package="xxx" /> 或者增加注解@ComponentScan({ "xxx"})。當時覺得挺urgly的,但也沒有去研究有沒有更好的方式。
直到接觸Spring Boot 后,發現其可以自動引入二方包的bean。不過一直沒有看這塊的實現原理。直到最近面試的時候被問到。所以就看了下實現邏輯。
使用姿勢
講原理前先說下使用姿勢。
在project A中定義一個bean。
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包。
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容器中。
收集bean定義類
在spring容器啟動時,會調用到AutoConfigurationImportSelector#getAutoConfigurationEntry
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一樣的處理方式注冊到容器中。
End
@EnableAutoConfiguration注解簡化了導入了二方包bean的成本。提供一個二方包給其他應用使用,只需要在二方包里將對外暴露的bean定義在spring.factories中就好了。對于不需要的bean,可以在使用方用@EnableAutoConfiguration的exclude屬性進行排除。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。