解析SpringBoot @EnableAutoConfiguration的使用
刚做后端开发的时候,最早接触的是基础的spring,为了引用二方包提供bean,还需要在xml中增加对应的包
直到接触SpringBoot后,发现其可以自动引入二方包的bean。不过一直没有看这块的实现原理。直到最近面试的时候被问到。所以就看了下实现逻辑。
使用姿势
讲原理前先说下使用姿势。
在projectA中定义一个bean。
packagecom.wangzhi;
importorg.springframework.stereotype.Service;
@Service
publicclassDog{
}
并在该project的resources/META-INF/下创建一个叫spring.factories的文件,该文件内容如下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog
然后在projectB中引用projectA的jar包。
projectA代码如下:
packagecom.wangzhi.springbootdemo;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.EnableAutoConfiguration;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.context.ConfigurableApplicationContext;
importorg.springframework.context.annotation.ComponentScan;
@EnableAutoConfiguration
publicclassSpringBootDemoApplication{
publicstaticvoidmain(String[]args){
ConfigurableApplicationContextcontext=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
protectedAutoConfigurationEntrygetAutoConfigurationEntry(
AutoConfigurationMetadataautoConfigurationMetadata,
AnnotationMetadataannotationMetadata){
if(!isEnabled(annotationMetadata)){
returnEMPTY_ENTRY;
}
//EnableAutoConfiguration注解的属性:exclude,excludeName等
AnnotationAttributesattributes=getAttributes(annotationMetadata);
//得到所有的Configurations
Listconfigurations=getCandidateConfigurations(annotationMetadata,
attributes);
//去重
configurations=removeDuplicates(configurations);
//删除掉exclude中指定的类
Setexclusions=getExclusions(annotationMetadata,attributes);
checkExcludedClasses(configurations,exclusions);
configurations.removeAll(exclusions);
configurations=filter(configurations,autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations,exclusions);
returnnewAutoConfigurationEntry(configurations,exclusions);
}
getCandidateConfigurations会调用到方法loadFactoryNames:
publicstaticListloadFactoryNames(Class>factoryClass,@NullableClassLoaderclassLoader){ //factoryClassName为org.springframework.boot.autoconfigure.EnableAutoConfiguration StringfactoryClassName=factoryClass.getName(); //该方法返回的是所有spring.factories文件中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类路径 returnloadSpringFactories(classLoader).getOrDefault(factoryClassName,Collections.emptyList()); } publicstaticfinalStringFACTORIES_RESOURCE_LOCATION="META-INF/spring.factories"; privatestaticMap >loadSpringFactories(@NullableClassLoaderclassLoader){ MultiValueMap result=cache.get(classLoader); if(result!=null){ returnresult; } try{ //找到所有的"META-INF/spring.factories" Enumeration urls=(classLoader!=null? classLoader.getResources(FACTORIES_RESOURCE_LOCATION): ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result=newLinkedMultiValueMap<>(); while(urls.hasMoreElements()){ URLurl=urls.nextElement(); UrlResourceresource=newUrlResource(url); //读取文件内容,properties类似于HashMap,包含了属性的key和value Propertiesproperties=PropertiesLoaderUtils.loadProperties(resource); for(Map.Entry,?>entry:properties.entrySet()){ StringfactoryClassName=((String)entry.getKey()).trim(); //属性文件中可以用','分割多个value for(StringfactoryName:StringUtils.commaDelimitedListToStringArray((String)entry.getValue())){ result.add(factoryClassName,factoryName.trim()); } } } cache.put(classLoader,result); returnresult; } catch(IOExceptionex){ thrownewIllegalArgumentException("Unabletoloadfactoriesfromlocation["+ FACTORIES_RESOURCE_LOCATION+"]",ex); } }
注册到容器
在上面的流程中得到了所有在spring.factories中指定的bean的类路径,在processGroupImports方法中会以处理@import注解一样的逻辑将其导入进容器。
publicvoidprocessGroupImports(){
for(DeferredImportSelectorGroupinggrouping:this.groupings.values()){
//getImports即上面得到的所有类路径的封装
grouping.getImports().forEach(entry->{
ConfigurationClassconfigurationClass=this.configurationClasses.get(
entry.getMetadata());
try{
//和处理@Import注解一样
processImports(configurationClass,asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()),false);
}
catch(BeanDefinitionStoreExceptionex){
throwex;
}
catch(Throwableex){
thrownewBeanDefinitionStoreException(
"Failedtoprocessimportcandidatesforconfigurationclass["+
configurationClass.getMetadata().getClassName()+"]",ex);
}
});
}
}
privatevoidprocessImports(ConfigurationClassconfigClass,SourceClasscurrentSourceClass,
CollectionimportCandidates,booleancheckForCircularImports){
...
//遍历收集到的类路径
for(SourceClasscandidate:importCandidates){
...
//如果candidate是ImportSelector或ImportBeanDefinitionRegistrar类型其处理逻辑会不一样,这里不关注
//CandidateclassnotanImportSelectororImportBeanDefinitionRegistrar->
//processitasan@Configurationclass
this.importStack.registerImport(
currentSourceClass.getMetadata(),candidate.getMetadata().getClassName());
//当作@Configuration处理
processConfigurationClass(candidate.asConfigClass(configClass));
...
}
...
}
可以看到,在第一步收集的bean类定义,最终会被以Configuration一样的处理方式注册到容器中。
End
@EnableAutoConfiguration注解简化了导入了二方包bean的成本。提供一个二方包给其他应用使用,只需要在二方包里将对外暴露的bean定义在spring.factories中就好了。对于不需要的bean,可以在使用方用@EnableAutoConfiguration的exclude属性进行排除。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。