自動裝配:
實際上就是如何將Bean自動化裝載到IOC容器中管理,Springboot 的自動裝配時通過SPI
的方式來實現的
SPI:SpringBoot 定義的一套接口規范,這套規范規定:Springboot 在啟動時會掃描外部引用 jar 包中的
META-INF/spring.factories
文件,將文件中配置的類型信息加載到 Spring 容器,并執行類中定義的各種操作。對于外部 jar 來說,只需要按照 Springboot 定義的標準,就能將自己的功能裝置進 Springboot。
Springboot 通過@EnableAutoConfiguration
開啟自動裝配,我們點進去發現一個@Import(AutoConfigurationImportSelector.class)
查看他的Diagrams發現它繼承于ImportSelector
在Spring源碼中見到過,那我們就找selectImports()
方法
AutoConfigurationImportSelector → selectImports()
這里就是自動裝配的核心代碼了getAutoConfigurationEntry
@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 (!this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;} else {AnnotationAttributes attributes = this.getAttributes(annotationMetadata);// 通過 SpringFactoriesLoader.loadSpringFactories() // 加載META-INF/spring.factories中的配置List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);// 去除重復項configurations = this.removeDuplicates(configurations);// 應用exclusion屬性,排除掉的引入// 開發過程中某些服務不需要的配置信息可以在注解后加上(exclude = xxxAutoConfiguration.class)來排除加載Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);this.checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);// 檢查候選配置類上的注解@ConditionalOnClass// 如果要求的類不存在,則這個候選類會被過濾不被加載configurations = this.getConfigurationClassFilter().filter(configurations);this.fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}
}
通過this.getCandidateConfigurations()
發現是SpringFactoriesLoader.loadSpringFactories()
加載META-INF/spring.factories
中的配置來實現的
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {Map<String, List<String>> result = (Map)cache.get(classLoader);if (result != null) {return result;} else {Map<String, List<String>> result = new HashMap();try {Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {Map.Entry<?, ?> entry = (Map.Entry)var6.next();String factoryTypeName = ((String)entry.getKey()).trim();String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());String[] var10 = factoryImplementationNames;int var11 = factoryImplementationNames.length;for(int var12 = 0; var12 < var11; ++var12) {String factoryImplementationName = var10[var12];((List)result.computeIfAbsent(factoryTypeName, (key) -> {return new ArrayList();})).add(factoryImplementationName.trim());}}}result.replaceAll((factoryType, implementations) -> {return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));});cache.put(classLoader, result);return result;} catch (IOException var14) {throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);}}
}
this.getConfigurationClassFilter().filter(configurations)
這里是如何過濾的我們繼續深入看一下在內部類ConfigurationClassFilter的實現
ConfigurationClassFilter.java中autoConfigurationMetadata
屬性在構造函數中進行了賦值,查看loadMetadata()
發現是根據PATH = "META-INF/spring-autoconfigure-metadata.properties"
文件中的信息進行判斷的
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";private AutoConfigurationMetadataLoader() {}static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {return loadMetadata(classLoader, PATH);}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(urls.nextElement())));}return loadMetadata(properties);}catch (IOException ex) {throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);}}
我們拿出META-INF/spring-autoconfigure-metadata.properties
中一部分數據
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration=
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration$MessagingTemplateConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.core.RabbitMessagingTemplate
分析發現如果項目中不存在org.springframework.amqp.rabbit.core.RabbitMessagingTemplate
這個類,那么在容器啟動時就不加載。也就是說我們用的時候直接引入這個starter,啟動的時候就會加載這個starter里的配置,不引入就不加載。META-INF/spring.factories
里的自動加載信息是為了方便我們后期引入不需要額外再進行配置。
總結:
Srpingboot使用@EnableAutoConfiguration
注解開啟自動裝配,通過SpringFactoriesLoader.*loadFactoryNames()*
加載META-INF/spring.factories
中的自動配置類實現自動裝配,通過@ConditionalOn...
按需加載的配置類過濾掉未引入用不到的項
@ConditionalOn
條件注解:
@ConditionalOnBean
:當容器里有指定 Bean 的條件下
@ConditionalOnMissingBean
:當容器里沒有指定 Bean 的情況下
@ConditionalOnSingleCandidate
:當指定 Bean 在容器中只有一個,或者雖然有多個但是指定首選 Bean
@ConditionalOnClass
:當類路徑下有指定類的條件下
@ConditionalOnMissingClass
:當類路徑下沒有指定類的條件下
@ConditionalOnProperty
:指定的屬性是否有指定的值
@ConditionalOnResource
:類路徑是否有指定的值
@ConditionalOnExpression
:基于 SpEL 表達式作為判斷條件
@ConditionalOnJava
:基于 Java 版本作為判斷條件
@ConditionalOnJndi
:在 JNDI 存在的條件下差在指定的位置
@ConditionalOnNotWebApplication
:當前項目不是 Web 項目的條件下
@ConditionalOnWebApplication
:當前項目是 Web 項 目的條件下