0


SpringBoot的核心注解及自动配置原理

文章目录


1. 前言

    SpringBoot的自动配置原理是一个令人头疼的问题,经过一下午的学习写下这篇文章供大家参考,如果有雷同纯属巧合。文章中如果出现错误,欢迎大家指出,本人会随时修改。
     SpringBoot版本:**2.7.5**(注意版本号,不然跟文章中调试结果可能不一致)。

2. SpringBoot的核心注解

@SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[] args){SpringApplication.run(DemoApplication.class, args);}}
    我们在创建的springboot项目时都会写一个主启动类,每次启动项目时只要运行主启动类中的main方法即可。标注在主启动类上的 @SpringBootApplication注解就是springboot的核心注解,但该注解是一个合成注解,主要由下面三个注解组成:
@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(
    excludeFilters ={@Filter(
    type =FilterType.CUSTOM,
    classes ={TypeExcludeFilter.class}),@Filter(
    type =FilterType.CUSTOM,
    classes ={AutoConfigurationExcludeFilter.class})})

@SpringBootConfiguration
@SpringBootConfiguration注解的作用是标注该类是一个配置类。它与@Configuration注解的功能一致,区别是@SpringBootConfiguration是springboot中的注解,而@Configuration注解是spring中的注解。由下面的代码可以看出@SpringBootConfiguration是@Configuration的一个派生注解。

@Documented@Configuration@Indexedpublic@interfaceSpringBootConfiguration{@AliasFor(
        annotation =Configuration.class)booleanproxyBeanMethods()defaulttrue;}

@ComponentScan
@ComponentScan注解的作用是扫描指定包及其子包中所有的类,将满村过滤器条件的类作为bean注册到spring容器中。默认情况下会扫描主启动类所在的包及其子包中所有的类。
@EnableAutoConfiguration
@EnableAutoConfiguration注解的作用是开启自动配置功能。加上该注解,程序会加载META-INF/spring.factories文件中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足指定的生效条件时,实例化AutoConfiguration类中定义的bean,最后注入到Spring容器中,这样就完成了依赖框架的自动配置。接下来的自动配置的讲解都是通过该注解进行展开。

3. SpringBoot的自动配置

@EnableAutoConfiguration:

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})public@interfaceEnableAutoConfiguration{StringENABLED_OVERRIDE_PROPERTY="spring.boot.enableautoconfiguration";Class<?>[]exclude()default{};String[]excludeName()default{};}
    @EnableAutoConfiguration注解主要由@AutoConfigurationPackage、@Import这两个注解组成。

3.1 @AutoConfigurationPackage注解

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Import(AutoConfigurationPackages.Registrar.class)public@interfaceAutoConfigurationPackage{}

Registrar类:

staticclassRegistrarimplementsImportBeanDefinitionRegistrar,DeterminableImports{@OverridepublicvoidregisterBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry){register(registry,newPackageImports(metadata).getPackageNames().toArray(newString[0]));}@OverridepublicSet<Object>determineImports(AnnotationMetadata metadata){returnCollections.singleton(newPackageImports(metadata));}}
    @AutoConfigurationPackage注解在底层使用@Import注解将Registrar导入到容器中。而Registrar类的作用是以编程的形式注册自动配置包名称,以记录包扫描的入口。那获取的是哪个包名呢?

在这里插入图片描述
在这里插入图片描述
我们看registerBeanDefinitions方法中的metadata参数(该参数表示注解元信息),其中包括注解的位置等信息,而注解的位置正是在启动类上面,所以获取的包名正是启动类所在的包。看上面的图,该代码计算出来的结果就是主启动类所在的包。
很多人在这里就有疑问了,启动类上面的注解是@SpringbootApplication,为什么能够获取该包名?其实metadata元信息中的注解位置指的是@AutoConfigurationPackage位置,而@SpringbootApplication是合成注解,里面包括了@EnableAutoConfiguration注解,而@EnableAutoConfiguration注解又包括了@AutoConfigurationPackage注解,所以metadata中的包名就是主启动类的包名。

3.2 @Import注解

    我们查看@EnableAutoConfiguration注解中第二个重要的注解@Import。其作用是将AutoConfigurationImportSelector类作为一个组件导入容器中。接下来我们对AutoConfigurationImportSelector类进行分析一下。
@OverridepublicString[]selectImports(AnnotationMetadata annotationMetadata){if(!isEnabled(annotationMetadata)){returnNO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry =getAutoConfigurationEntry(annotationMetadata);returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}
    selectImports方法返回的数组中有哪些组件,那么程序就会导入哪些组件。组件的信息获取是通过getAutoConfigurationEntry方法进行获取的。
protectedAutoConfigurationEntrygetAutoConfigurationEntry(AnnotationMetadata annotationMetadata){if(!isEnabled(annotationMetadata)){returnEMPTY_ENTRY;}AnnotationAttributes attributes =getAttributes(annotationMetadata);List<String> configurations =getCandidateConfigurations(annotationMetadata, attributes);
        configurations =removeDuplicates(configurations);Set<String> exclusions =getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations =getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);returnnewAutoConfigurationEntry(configurations, exclusions);}
    通过对getAutoConfigurationEntry方法进行分析,我们可以看出该方法还是要调用getCandidateConfigurations方法来获取数据,然后移除重复的组件等一系列操作后在返回给selectImports方法,接下来我们继续分析getCandidateConfigurations方法。
protectedList<String>getCandidateConfigurations(AnnotationMetadata metadata,AnnotationAttributes attributes){List<String> configurations =newArrayList<>(SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader()));ImportCandidates.load(AutoConfiguration.class,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;}
publicstaticList<String>loadFactoryNames(Class<?> factoryType,@NullableClassLoader classLoader){ClassLoader classLoaderToUse = classLoader;if(classLoader ==null){
            classLoaderToUse =SpringFactoriesLoader.class.getClassLoader();}String factoryTypeName = factoryType.getName();return(List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName,Collections.emptyList());}
privatestaticMap<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 =newHashMap();try{Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");while(urls.hasMoreElements()){URL url =(URL)urls.nextElement();UrlResource resource =newUrlResource(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)->{returnnewArrayList();})).add(factoryImplementationName.trim());}}}
    为了不讲废话了,直接将所有代码粘出来一起分析。整体逻辑就是getCandidateConfigurations方法调用loadFactoryNames方法,然后loadFactoryNames方法调用loadSpringFactories方法得到的,我们直接分析loadSpringFactories即可。
     我们从loadSpringFactories方法的第8行可以知道,组件的获取是扫描META-INF文件夹下的spring.factories得到的。

结果:
spring.factories文件:
在这里插入图片描述
configuration数量:
在这里插入图片描述
哈哈哈,是不是发现一个新的问题。。。没错,spring.factories文件中远远没有144个。经过我的努力,终于找到了问题所在。在springboot2.7.5的版本中,需要加载的自动配类已经放到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中,而不是写在spring.factories文件中。
官网原文:
在这里插入图片描述
在这里插入图片描述
在这个文件中刚好有144条需要加载的配置类。

4. 按需开启自动配置

    上面讲解了springboot如何加载所有的自动配置类,但在实际上,并不会加载所有的自动配置类,而是按需加载。如何按需加载呢,我们举两个例子。

4.1 以AopAutoConfiguration为例

@AutoConfiguration@ConditionalOnProperty(prefix ="spring.aop", name ="auto", havingValue ="true", matchIfMissing =true)publicclassAopAutoConfiguration{@Configuration(proxyBeanMethods =false)@ConditionalOnClass(Advice.class)staticclassAspectJAutoProxyingConfiguration{@Configuration(proxyBeanMethods =false)@EnableAspectJAutoProxy(proxyTargetClass =false)@ConditionalOnProperty(prefix ="spring.aop", name ="proxy-target-class", havingValue ="false")staticclassJdkDynamicAutoProxyConfiguration{}@Configuration(proxyBeanMethods =false)@EnableAspectJAutoProxy(proxyTargetClass =true)@ConditionalOnProperty(prefix ="spring.aop", name ="proxy-target-class", havingValue ="true",
                matchIfMissing =true)staticclassCglibAutoProxyConfiguration{}}@Configuration(proxyBeanMethods =false)@ConditionalOnMissingClass("org.aspectj.weaver.Advice")@ConditionalOnProperty(prefix ="spring.aop", name ="proxy-target-class", havingValue ="true",
            matchIfMissing =true)staticclassClassProxyingConfiguration{@BeanstaticBeanFactoryPostProcessorforceAutoProxyCreatorToUseClassProxying(){return(beanFactory)->{if(beanFactory instanceofBeanDefinitionRegistry){BeanDefinitionRegistry registry =(BeanDefinitionRegistry) beanFactory;AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}};}}}
    在AopAutoConfiguration类上面加上了@ConditionalOnProperty注解,表明配置文件中如果存在spring.aop.auto=true,那么则加载该配置类,matchIfMissing = true表示就算配置文件没有配置,该配置类也生效。
     上面的第5行代码(@ConditionalOnClass(Advice.class))表示如果Advice类存在,那么AspectJAutoProxyingConfiguration配置类则生效;上面第20行代码(@ConditionalOnMissingClass(“org.aspectj.weaver.Advice”))表示Advice类不存在则ClassProxyingConfiguration配置类生效。
     **由此可知,当没有导入aspectj相关的jar包时,springboot中AOP默认使用JDK动态代理来实现。**

4.2 以BatchAutoConfiguration为例

@AutoConfiguration(after =HibernateJpaAutoConfiguration.class)@ConditionalOnClass({JobLauncher.class,DataSource.class})@ConditionalOnBean(JobLauncher.class)@EnableConfigurationProperties(BatchProperties.class)@Import({BatchConfigurerConfiguration.class,DatabaseInitializationDependencyConfigurer.class})publicclassBatchAutoConfiguration{}
    如上面的代码(第二第三行代码),当JobLauncher类存在且JobLauncher对应的bean存在于容器时,该配置类才会生效,否则不会开启该配置。
     **总结:在springboot中默认会加载所有的配置类且开启按需自动配置类,自动配置功能是通过@ConditionalOnClass、@ConditionalOnProperty和@ConditionalOnMissingClass等一系列注解实现的。**

5. 总结

  • SpringBoot中的核心注解是@SpringBootApplication,不过该注解是一个合成注解,主要是由@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan等三个注解组成。
  • SpringBoot的自动配置是通过扫描spring-boot-configuration中META-INF文件夹下的spring.factories文件,然后加载对应的配置类来实现的。
  • SpringBoot的自动配置不会加载所有的配置类,而是按需配置,主要是通过@ConditionalOnClass、@ConditionalOnProperty和@ConditionalOnMissingClass等一系列注解实现的。

注意:在以前的版本中,SpringBoot会扫描META-INF文件夹下的spring.factories文件,在SpringBoot2.7.5(从哪个版本开始不知道)中改成了spring文件夹下的org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。

参考:【尚硅谷】SpringBoot2零基础入门教程

标签: spring boot java spring

本文转载自: https://blog.csdn.net/m0_73845616/article/details/127948559
版权归原作者 never debug 所有, 如有侵权,请联系我们删除。

“SpringBoot的核心注解及自动配置原理”的评论:

还没有评论