自动装配:
实际上就是如何将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 项 目的条件下