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