当前位置: 首页 > 后端技术 > Java

SpringBoot-自动配置-原理分析

时间:2023-04-01 23:55:35 Java

SpringBoot自动配置原理分析文章已经同步到GitHub开源项目:Java超神之路SpringBoot的主要宗旨是约定大于配置。在开发项目的前期,我们不需要做太多的配置,SpirngBoot已经为我们自动配置了大部分内容,比如仲裁依赖机制、自动引入所需依赖、自动配置等内容。让我们更关注业务逻辑,那么它是如何实现自动配置的呢?首先我们可以看到在SpringBoot的启动类上有一个@SpringBootApplication注解。接下来我们分析这个注解。点进去发现主要是由下面的注解组成。@SpringBootConfiguration//表示这是一个配置类@EnableAutoConfiguration@ComponentScan//我们一一分析包扫描规则。@SpringBootConfiguration点进去发现是一个Configuration@Configuration@Indexedpublic@interfaceSpringBootConfiguration{@AliasFor(annotation=Configuration.class)booleanproxyBeanMethods()defaulttrue;}我们在Spring中学习过这个注解,它代表当前一个配置类,所以在SpringBootApplication中标注的@SpringBootConfiguration注解是用来将这个启动类标注为配置类的。@ComponentScan我们从之前的Spring也知道,这个注解的意思是在注册IoC容器的时候,按照这个注解指定的方式进行包扫描,不用太担心。@EnableAutoConfiguration@AutoConfigurationPackage//通过主程序包名批量注册@Import(AutoConfigurationImportSelector.class)//public@interfaceEnableAutoConfiguration{StringENABLED_OVERRIDE_PROPERTY="spring.boot.enableautoconfiguration";类[]排除()默认{};String[]excludeName()default{};}这个注解主要由两个注解组成。下面一一分析@AutoConfigurationPackage:自动配置包@Import(AutoConfigurationPackages.Registrar.class)//通过主程序包名批量注册public@interfaceAutoConfigurationPackage{String[]basePackages()default{};类[]basePackageClasses()default{};}我们发现这个注解通过@Import(AutoConfigurationPackages.Registrar.class)将一个组件AutoConfigurationPackages.Registrar导入到IoC容器中。我们点进去发现这是由两个方法类组成的staticclassRegistrarimplementsImportBeanDefinitionRegistrar,DeterminableImports{@OverridepublicvoidregisterBeanDefinitions(AnnotationMetadatametadata,BeanDefinitionRegistryregistry){register(registry,newPackageImports(metadata).getPackageNames().toArray(新));String[0]}@OverridepublicSetdetermineImports(AnnotationMetadatametadata){returnCollections.singleton(newPackageImports(metadata));}}}我们在这里打了断点,然后进行Debug分析。我们发现该方法将一系列组件导入到容器中。通过Debug,元数据参数代表最原始的SpringBootApplication启动类。通过代码我们可以看到它新建了一个PackageImports对象,将启动类传入,然后调用了getPackageNames()方法获取了一个包名,debug发现返回的包名是cn.shaoxiongdu中的包名我们自己的项目,然后发现它把包名封装成一个String数组作为参数,调用了register方法。所以register方法就是通过包名,也就是主程序类所在的包,对组件进行批量注册。所以这就是为什么默认的包扫描规则是主程序类所在的包。所以第一部分注解EnableAutoConfiguration,AutoConfigurationPackage的作用是通过主程序的包名进行批量注册。接下来我们看第二个注解。@Import(AutoConfigurationImportSelector.class)我们发现这是一个类,点进去,发现主要方法如下}AutoConfigurationEntryautoConfigurationEntry=getAutoConfigurationEntry(annotationMetadata);返回StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());Discoverbymethodname该方法返回我们需要在容器中注册的bean名称数组。所以我们的重点在这里。AutoConfigurationEntryautoConfigurationEntry=getAutoConfigurationEntry(annotationMetadata);//我们需要为容器中注册的bean名称数组进入这个方法,我们继续分析这个方法。protectedAutoConfigurationEntrygetAutoConfigurationEntry(AnnotationMetadataannotationMetadata){if(!isEnabled(annotationMetadata)){返回EMPTY_ENTRY;}AnnotationAttributes属性=getAttributes(annotationMetadata);List配置=getCandidateConfigurations(annotationMetadata);//需要注册的属性)候选组件configurations=removeDuplicates(configurations);//删除重复的组件Setexclusions=getExclusions(annotationMetadata,attributes);checkExcludedClasses(配置,排除);配置.removeAll(排除);配置=getConfigurationClassFilter().filter(配置);fireAutoConfigurationImportEvents(配置,排除);返回新的AutoConfigurationEntry(配置,排除);通过Debug,我们发现当执行到第7行时,configurations列表中已经有100多个bean名了。操作是对List集合进行一些正常的处理并返回。所以我们只需要分析第6行的方法getCandidateConfigurations(annotationMetadata,attributes);它返回我们需要在容器中默认注册的bean名称的字符数组。我们再调试一下,进入方法protectedListgetCandidateConfigurations(AnnotationMetadatametadata,AnnotationAttributesattributes){Listconfigurations=SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());//获取需要注册的组件集合Assert.notEmpty(configurations,"NoautoconfigurationclassesfoundinMETA-INF/spring.factories.如果你"+"使用的是自定义包装,确保文件正确”);返回配置;}通过分析,我们发现main进程在第2行,通过工厂模式加载了需要注册的容器集合。继续调试这个方法如果(classLoader==null){classLoaderToUse=SpringFactoriesLoader.class.getClassLoader();}StringfactoryTypeName=factoryType.getName();返回(列表)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName,Collections.emptyList());//返回需要注册的组件集合}重点在最后一行,通过loadSpringFactories方法返回对应的集合。继续调试进入此方法privatestaticMap>loadSpringFactories(ClassLoaderclassLoader){Map>result=(Map)cache.get(classLoader);如果(结果!=null){返回结果;}else{HashMap结果=newHashMap();尝试{枚举urls=classLoader.getResources("META-INF/spring.factories");while(urls.hasMoreElements()){URLurl=(URL)urls.nextElement();UrlResource资源=newUrlResource(url);属性properties=PropertiesLoaderUtils.loadProperties(resource);迭代器var6=properties.entrySet().iterator();while(var6.hasNext()){Entryentry=(Entry)var6.next();StringfactoryTypeName=((String)entry.getKey()).trim();英石ring[]factoryImplementationNames=StringUtils.commaDelimitedListToStringArray((String)entry.getValue());String[]var10=factoryImplementationNames;intvar11=factoryImplementationNames.length;for(intvar12=0;var12{returnnewArrayList();})).add(factoryImplementationName.trim());}}}result.replaceAll((factoryType,implementations)->{return(List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(),Collections::unmodifiableList));});cache.put(classLoader,result);返回结果;}catch(IOExceptionvar14){thrownewIllegalArgumentException("无法从[META-INF/spring.factories]位置加载工厂",var14);}}}这个方法返回需要注册的工厂我们可以分析这个方法来进行组件收集。首先debug发现代码来到了第6行,创建了一个HashMap。然后在try中,我们发现它加载了一个资源文件META-INF/spring.factories,并在所有的依赖中循环扫描这个文件。通过检查,我们发现大部分依赖都有这个文件,少数没有。我们打开spring-boot-autoconfiguration依赖,打开他的spring.factories文件,里面有一个key为org.springframework.boot.autoconfigure.EnableAutoConfiguration。该项目的值称为XXXConfiguration。一个XXXConfiguration对应一个依赖的自动配置类。也就是说,在spring-boot-autoconfiguration依赖的spring.factories文件中,spring-boot一启动就会加载到容器中的所有配置类,我们运行下面的Main方法publicstaticvoidmain(String[]args){//返回IoC容器ConfigurableApplicationContextrun=SpringApplication.run(Springboot01HelloApplication.class,args);intbeanDefinitionCount=run.getBeanDefinitionCount();System.out.println("beanDefinitionCount="+beanDefinitionCount);}仅找到143个结果。也就是说,并不是所有的组件都会注册到容器中。通过查看这个依赖中的一些配置类,我们发现大部分类都会有@Conditional注解,也就是说在容器中注册是有条件的,并不保证一定会被加载。只有满足条件才会加载。结论对于我们自定义的组件:通过@AutoConfigurationPackage注解,最终调用register(registry,newPackageImports(metadata).getPackageNames();方法获取启动类包下的组件进行循环注册。对于其他组件:SpringBoot先加载所有自动配置类xxxxxAutoConfiguration根据条件生效,默认会绑定配置文件指定的值,在xxxxProperties中获取,如果xxxProperties绑定在配置文件上,满足条件,则注册在容器中自定义配置用户直接将底层组件替换为自己的@Bean,用户可以修改该组件获取的配置文件的值。xxxxxAutoConfiguration--->Components--->xxxxProperties取值---->application.properties以上就是SpringBoot自动配置功能的底层原理。文章已同步至GitHub开源项目:Java超神之路更多Java知识,欢迎访问!