刚做后端开发的时候,最早接触的是基础的spring,为了引用二方包提供bean,还需要在xml中增加对应的包

在永福等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供成都网站建设、做网站 网站设计制作按需求定制开发,公司网站建设,企业网站建设,成都品牌网站建设,成都全网营销,成都外贸网站建设公司,永福网站建设费用合理。
直到接触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 configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 去重
configurations = removeDuplicates(configurations);
// 删除掉exclude中指定的类
Set 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 ListloadFactoryNames(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 > loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap result = cache.get(classLoader); if (result != null) { return result; } try { // 找到所有的"META-INF/spring.factories" Enumeration 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 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属性进行排除。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持创新互联。
本文名称:解析SpringBoot@EnableAutoConfiguration的使用
当前路径:http://www.jxjierui.cn/article/iicseg.html


咨询
建站咨询
