解析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中指定的类 Set exclusions=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属性进行排除。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。