与Spring自动装配的区别
Spring的自动装配,我们至少需要写一个配置文件,无论是什么形式,我们都至少需要一个文件把它全部写下来,就算这个文件的内容是固定的,但是为了装配这个对象,我们不得不写。
于是就诞生了一个想法:一个配置文件都不想写,程序还能照样跑,我只关心有我需要的组件就可以了,我只需要关注我的目标就可以了,我想打开一个工程之后可以1秒进入开发状态,而不是花3小时写完配置文件于是!SpringBoot的自动装配诞生了。
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包所有 Spring Boot Starter 下的META-INF/spring.factories文件,来加载根目录以外的bean(就是maven坐标依赖的jar包),将文件中配置的类型信息加载到 Spring 容器,并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
结论先行
@SpringBootApplication
主启动类注解,是一个复合注解,他包含了以下注解:
@Target
@Retention
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
其中@EnableAutoConfiguration
注解开启自动配置,加载spring.factories
文件中注册的各种AutoConfiguration
。
@EnableAutoConfiguration
也是一个复合注解
@Target
@Retention
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
当解析到@Import({AutoConfigurationImportSelector.class})
这一行注解的时候,会通过反射创建AutoConfigurationImportSelector
对象,从缓存中读取/META-INF/spring.factories
文件里的k-v。并经过一系列的过滤、去重等,最后将需要的配置类加载,生成BD对象,创建Bean对象,放入spring容器。
接下来对这一过程进行源码分析
AutoConfigurationImportSelector
类中有一个方法会被自动调用:selectImports()
入参表示注解元数据。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
它的返回值是一个String[]数组,可以推断出来这个数组里的元素就是我们需要向Spring容器导入的东西。这个数组是从getAutoConfigurationEntry
方法中得到的
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
}
其中:
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
这一行是获取所有需要导入到容器中的配置类。
后续对configuration进行一些排查操作,最后封装好返回。我们进入到getCandidateConfigurations方法。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
可以看到,我们的集合在这个方法内的名称是configurations,而他,是通过SpringFactoriesLoader.loadFactoryNames方法得到的。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
// 现将内容加载到一个map中,再从map中加载列表,相当于一个缓存
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
同理,我们再进入到loadSpringFactories方法
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 {
// 加载到当前应用的所有的spring.factories文件的位置
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);
}
}
}
loadSpringFactories
方法将/META-INF/spring.factories
文件里的k-v读入缓存。第一次读取,后续就直接从缓存中拿。
Spring.factories文件内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.boot.nacos.config.autoconfigure.NacosConfigAutoConfiguration
#org.springframework.context.ApplicationContextInitializer=\
# com.alibaba.boot.nacos.config.autoconfigure.NacosConfigApplicationContextInitializer
org.springframework.boot.env.EnvironmentPostProcessor=\
com.alibaba.boot.nacos.config.autoconfigure.NacosConfigEnvironmentProcessor
org.springframework.context.ApplicationListener=\
com.alibaba.boot.nacos.config.logging.NacosLoggingListener
我们看到,这个文件配置了一个key:value格式的数据
1)key是:org.springframework.boot.env.EnvironmentPostProcessor
2)value是:com.alibaba.boot.nacos.config.autoconfigure.NacosConfigEnvironmentProcessor
简单来说,key是接口,value是对应的实现类。我们通过这些配置就可以知道接口有哪些可选的实现类,并通过反射获取对应的实例对象