开发者

SpringBoot中自动配置原理解析

开发者 https://www.devze.com 2023-11-20 11:27 出处:网络 作者: F
目录前言一、SpringBoot自动配置是什么二、@Import注解1. 方式一: .class方式2.方式二:ImportSelector方式3.方式三:ImportBeanDefinitionRegistrar方式三、SpringBoot自动配置原理解析总结前言
目录
  • 前言
  • 一、SpringBoot自动配置是什么
  • 二、@Import注解
    • 1. 方式一: .class方式
    • 2.方式二:ImportSelector方式
    • 3.方式三:ImportBeanDefinitionRegistrar方式
  • 三、SpringBoot自动配置原理解析
    • 总结

      前言

      SpringBoot作为当前Java开发的热门框架,有绞手架之称。“约定大于配置”也一直是SpringBoot的标签,那么,SpringBoot要实现自身优势,自动配置功不可没。

      一、SpringBoot自动配置是什么

      SpringBoot自动配置是指在应用程序启动时,SpringBoot根据classpath路径下的jar包自动配置应用程序所需的一系列bean和组件,从而减少开发者的配置工作,提高开发效率。

      二、@Import注解

      在剖析SpringBoot自动配置原理之前,我们先了解一下@Import注解的使用

      1. 方式一: .class方式

      定义两个类A、B,并将其加入到Spring IOC容器中:

      @Data
      public class A{
      	private Integer id=0;
      	private String name="classA";
      	public void print(){
      		System.out.println(this.name);
      	}
      }
      
      @Data
      public class B{
      	private Integer id=1;
      	private String name="classB";
      	public void print(){
      		System.out.println(this.name);
      	}
      }
      

      创建一个配置类,并使用@Import注解将类A、B添加到 IOC 容器中:

      @Import({A.class,B.class})
      @Configuration
      public class ClassConfig{
      ​​​​​​​}
      

      2.方式二:ImportSelector方式

      该方法需要定义类来实现ImportSelector接口,并重写其中的selectImports( )方法,该方法的返回值是需要添加到IOC容器中的类的全限定类名数组:

      @Data
      public class C{
          private Integer id=2;
          private String name="classC";
          public void print(){
              System.out.println(this.name);
          }
      }
      

      编写类来实现ImportSelector接口:

      public class ImportSelectorTest implements ImportSelector{
          @Override
          public String[] selectImports(AnnotationMetadata annotationMetada){
              return new String[]{"C.class"};
          }
      }
      

      使用该类:

      @Import({ImportSelectorTest.class})
      @Configuration
      public class ImportSelectorConfig{
      ​​​​​​​}
      

      3.方式三:ImportBeanDefinitionRegistrar方式

      定义一个类并实现ImportBeanDefinitionRegistrar接口,重写其中的registerBeanDefinitions( )方法,此方式可以自定义Bean在容器中的名称:

      @Data
      public class D{
      	private Integer id=3;
      	private String name="ClassD";
      	public void print(){
      		System.out.println(this.name);
      	}
      }
      
      //定义类实现ImPortBeanDefinitionRegistrar接口,重写其中的registerBeanDefinitions()方法
      public class ImportBeanDefinitionRegistrarTest{
      	@Override
      	public void registerBeanDefninitions(AnnotationMetadata annotationMetadata,BeanDefinitionRegistry registry{
      		RootBeanDefinition rootBeanDefinition=new RootBeanDefinition(D.class);
      		registry.registerBeanDefinition("自定义名称",rootBeanDefinition);
      	}
      }
      
      //使用上面的类进行导入
      @Import({ImportBeanDefinitionRegistrarTest})
      @Configuration
      public class ImportBeanDefinitionRegistrarConfig{
      
      }
      

      三、SpringBoot自动配置原理解析

      为了容易分析和理解,我们在IDEA中创建一个SpringBoot项目,创建过程省略,直接跳到该项目的主配置类进行分析:

      @SpringBootApplication
      public class SpringBootTestApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(SpringBootTestApplication.class, args);
          }
      
      }
      

      其中@SpringBootApplication注解是SpringBoot项目的重点,按住ctrl键进入其中,看到它由以下部分组成:

      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Inherited
      @SpringBootConfiguration
      @EnableAutoConfiguration
      @ComponentScan(
          excludeFilters = {@Filter(
          type = FilterType.CUSTOM,
          classes = {TypeExcludeFilter.class}
      ), @Filter(
          type = FilterType.CUSTOM,
          classes = {AutoConfigurationExcludeFilter.class}
      )}
      )
      public @interface SpringBootApplication {
          //内容省略
      }
      

      我们主要关注以下几个注解:

      • @SpringBootConfiguration:标记当前类为配置类
      • @EnableAutoConfiuration:开启自动配置
      • @ComponentScan:扫描主类所在包及其子包、同级包中的Bean

      1、@SpringBootConfiguration注解:标记当前类为配置类

      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Configuration
      @Indexed
      public @interface SpringBootConfiguration {
          @AliasFor(
              annotation = Configuration.class
          )
          boolean proxyBeanMethods() default true;
      }
      

      根据其源码可以知道,@SpringBootConfiguration注解包含@Configuration,所以其拥php有@Configuration注解相似的功能,而@Configuration注解又包含@Companent注解,所以配置类也存在于IOC容器中。

      2、@EnableAutoConfiguration注解:开启自动配置

      @Target({ElejavascriptmentType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Inherited
      @AutoConfigurationPackage
      @Import({AutoConfigurationImportSelector.class})
      public @interface EnableAutoConfiguration {
          String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
      
          Class<?>[] exclude() default {};
      
          String[] excludeName() default {};
      }
      

      根据其源码得出其主要由@AutoConfigurationPackages注解和@Import注解组成

      @AutoConfigurationPackages注解

      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Inherited
      @Import({Registrar.class})
      public @interface AutoConfigurationPackage {
          String[] basePackages() default {};
      
          Class<?>[] basePackageClasses() default {};
      }
      
      //其中的@Import注解导入了Registrar类,该类是AutoConfigurationPackages的静态类部类
      static clandroidass Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
              Registrar() {
              }
      
      		/**
      			根据传入的元注解信息获取所在的包,将包中组件类封装为数组进行注册
      		*/
              public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                  AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
              }
      
              public Set<Object> determineImports(AnnotationMetadata metadata) {
                  return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
              }
          }
      

      @Import({AutoConfigurationImportSelector.class})注解

      这一步就用到了@Import注解使用方式中的第二中:实现ImportSelector接口

      那么AutoConfigurationImportSelector接口何时如何被执行呢?

      SpringBoot 启动时会使用 ConfigurationClassParser 来解析被 @Configuration 标识的配置类, 然后再处理这个类内部被其他注解修饰的情况, 比如 @Import 注解, @ComponentScan 注解,@Bean 注解等

      若发现注解中存在 @Import(ImportSelector) 的情况下,就会创建一个相应的 ImportSelector 对象,并调用其 process 方法

      public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
                  Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> {
                      return String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName());
                  });
                  AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector)deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
                  this.autoConfigurationEntries.add(autoConfigurationEntry);
                  Iterator var4 = autoConfigurationEntry.gpythonetConfigurations().iterator();
      
                  while(var4.hasNext()) {
                      String importClassName = (String)var4.next();
                      this.entries.putIfAbsent(importClassName, annotationMetadata);
                  }
      
              }
      

      process方法又调用了getAutoConfigurationEntry方法:

      protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
              if (!this.isEnabled(annotationMetadata)) {
                  return EMPTY_ENTRY;
              } else {
                  AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                  List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                  configurations = this.removeDuplicates(configurations);
                  Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
                  this.checkExcludedClasses(configurations, exclusions);
                  configurations.removeAll(exclusions);
                  configurations = this.getConfigurationClassFiljster().filter(configurations);
                  this.fireAutoConfigurationImportEvents(configurations, exclusions);
                  return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
              }
          }
      

      getAutoConfigurationEntry方法调用了getCandidateConfigurations方法:

      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
              List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
              Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
              return configurations;
          }
      

      getCandidateConfigurations方法中使用了Spring Cor中的类SpringFactoriesLoader,该类的loadFactoryNames方法可以根据接口获取接口类的名称,这个方法返回的是类名的列表,loadFactoryNames方法会遍历整个springboot项目的classpath下的ClassLoader中所有jar包下的spring.factories文件。至此自动配置结束

      总结

      SpringBoot自动配置是SpringBoot的核心,所以了解SpringBoot的自动配置是非常有必要的,大家可以自行查找资料解释以下为什么不使用@ComponentScan注解替换@Import注解来进行类的导入

      以上就是SpringBoot中自动配置原理解析的详细内容,更多关于SpringBoot自动配置的资料请关注编程客栈(www.devze.com)其它相关文章!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号