写点什么

Spring Boot 自动配置原理详解和自定义封装实现 starter

  • 2023-04-08
    湖南
  • 本文字数:8765 字

    阅读完需:约 29 分钟

一、概述

之前我们对Spring的注解导入 @Import 和 注解扫描 @ComponentScan 分别进行了详细的总结,不清楚的可以点击链接自行阅读了解,基于这些总结的知识点,我们今天可以来分析一下Spring Boot自动配置的实现原理和自己手动封装一个 starter 了。


我们一直在强调Spring Boot能成为当下主流首选开发框架的主要原因在于其核心思想:约定大于配置,自动配置,条件装配。基于这些特性使得Spring Boot集成其他框架非常简单快捷。


使用Spring Boot创建的项目启动、执行也非常简单,只需要执行启动类的 main()方法即可,不需要做其他操作,Spring Boot会自动装配相关所需依赖和配置。

@SpringBootApplicationpublic class CommonDemoApplication {
   public static void main(String[] args) {   SpringApplication.run(CommonDemoApplication.class, args);   }
}
复制代码

二、Spring Boot 自动配置原理

从上面项目启动类可以看出,没有什么复杂的启动逻辑,就只使用一个注解@SpringBootApplication,这就是Spring Boot自动配置的核心入口所在,其定义如下:

@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 {
// 排除掉自动配置的class @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {};
 // 排除掉自动配置的全路径类名 @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {};
 // 配置扫描的包路径 @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {};
 // 配置扫描的类 @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {};
// beanName生成器 @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator") Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
 // 配置类代理模式:proxyBeanMethods:代理bean的方法  //     Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】  //     Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】 @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true;
}
复制代码

从定义可知@SpringBootApplication是一个复合注解,所以接下来我们逐一看看其关联使用的注解。

2.1 @SpringBootConfiguration

@SpringBootConfiguration的定义如下:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Configurationpublic @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true;
}
复制代码

从定义可知,该注解就是一个配置类注解,其作用和属性和@Configuration注解一样,只是这里语义化罢了,就像@Controller@Component一个道理。

2.2 @EnableAutoConfiguration

从名字上看,@EnableAutoConfiguration是自动配置核心所在,先看看其定义:

@Target(ElementType.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 {};
}
复制代码

可以看出@EnableAutoConfiguration也是一个复合注解,所以我们接下来对其关联的注解进行解析:

2.2.1 @AutoConfigurationPackage

该注解的作用是将添加该注解的类所在的 package 作为自动配置 package 进行管理,也就是说当 Spring Boot 应用启动时默认会将启动类所在的 package 作为自动配置的 package。老规矩,先看看其定义:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Import(AutoConfigurationPackages.Registrar.class)public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
复制代码

AutoConfigurationPackages.Registrar.class#register()

public static void register(BeanDefinitionRegistry registry, String... packageNames) {    if (registry.containsBeanDefinition(BEAN)) {      BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);      ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();      constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));    }    else {      GenericBeanDefinition beanDefinition = new GenericBeanDefinition();      beanDefinition.setBeanClass(BasePackages.class);      beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);      beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);      registry.registerBeanDefinition(BEAN, beanDefinition);    }  }
复制代码

这里就是简单注册自动配置包名,方便后续引用,如Spring Boot集成第三方 OMR 框架mybatis-plus,我们如下编写代码就能把 DAO 类注入到 Spring 容器中:

@Mapperpublic interface BrandDAO extends BaseMapper<Brand> {}
复制代码

@Mappermybatis框架中的注解,并不是 Spring 框架中的注解,那么Spring Boot集成mybatis之后是怎么做到自动扫描@Mapper进行注入的呢?这时候我们就要关注到mybatis-plus的 stater 的自动配置类MybatisPlusAutoConfiguration的内部类

    public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {        // 当前bean工厂容器        private BeanFactory beanFactory;
       public AutoConfiguredMapperScannerRegistrar() {       }
       // 注册beanDefinition        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {            // 判断是否注册了自动配置包            if (!AutoConfigurationPackages.has(this.beanFactory)) {                MybatisPlusAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");           } else {                MybatisPlusAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");                // 获取到之前注册的所有自动配置包路径                List<String> packages = AutoConfigurationPackages.get(this.beanFactory);                if (MybatisPlusAutoConfiguration.logger.isDebugEnabled()) {                    packages.forEach((pkg) -> {                        MybatisPlusAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);                   });               }
               // 构建beanDefinition                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);                builder.addPropertyValue("processPropertyPlaceHolders", true);                // 指定扫描主机@Mapper                builder.addPropertyValue("annotationClass", Mapper.class);                // 指定扫描的包路径,也就是前面注册的自动配置包路径                builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));                BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);                Stream.of(beanWrapper.getPropertyDescriptors()).filter((x) -> {                    return x.getName().equals("lazyInitialization");               }).findAny().ifPresent((x) -> {                    builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");               });                registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());           }       }
       public void setBeanFactory(BeanFactory beanFactory) {            this.beanFactory = beanFactory;       }   }
复制代码

可以看出: @AutoConfigurationPackage@ComponentScan一样,都是将Spring Boot启动类所在的包及其子包里面的组件扫描到 IOC 容器中,但是区别是@AutoConfigurationPackage扫描@Enitity、@Mapper等第三方依赖的注解,@ComponentScan只扫描@Controller/@Service/@Component/@Repository这些常见注解。所以这两个注解扫描的对象是不一样的。当然这只是直观上的区别,更深层次说,@AutoConfigurationPackage 是自动配置的体现,是 Spring Boot 中注解,而 @ComponentScan 是 Spring 的注解

2.2.2 @Import(AutoConfigurationImportSelector.class)

这个注解配置是Spring Boot的自动装配核心所在,需重点关注AutoConfigurationImportSelector,定义如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,    ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {    }
复制代码

可以看到AutoConfigurationImportSelector除了实现一系列的aware接口获取相关信息之外,就是实现了DeferredImportSelector接口,DeferredImportSelectorImportSelector的子接口,Deferred延迟的意思。


根据之前我们总结的 @Import 的使用和实现原理 可知,对@Import的解析会来到ConfigurationClassParser#processImports(),方法代码片段如下:

if (selector instanceof DeferredImportSelector) {  this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);}else {  String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());  Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);  processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);}
复制代码

这里会判断当前selectorDeferredImportSelector还是ImportSelector,如果是ImportSelector,才会执行其#selectImports()方法;如果是DeferredImportSelector,会进入执行this.deferredImportSelectorHandler.handle(),该方法会把DeferredImportSelector封装成DeferredImportSelectorHolder放入到this.deferredImportSelectors集合中。


根据DeferredImportSelector意思来看,就是延迟注入的意思,所以他会等 Spring 对配置类相关其他注解进行解析完之后,才执行这里的注入逻辑,可从ConfigurationClassParser#parse()方法得到验证:

  public void parse(Set<BeanDefinitionHolder> configCandidates) {    for (BeanDefinitionHolder holder : configCandidates) {      BeanDefinition bd = holder.getBeanDefinition();      try {        if (bd instanceof AnnotatedBeanDefinition) {          parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());        }        else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {          parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());        }        else {          parse(bd.getBeanClassName(), holder.getBeanName());        }      }      catch (BeanDefinitionStoreException ex) {        throw ex;      }      catch (Throwable ex) {        throw new BeanDefinitionStoreException(            "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);      }    }    // 等上面的解析完成之后再执行    this.deferredImportSelectorHandler.process();  }
复制代码

来到DeferredImportSelectorHolder#process()方法:

public void process() {      List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;      this.deferredImportSelectors = null;      try {        if (deferredImports != null) {          DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();          deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);          // 遍历调用handler的register()          deferredImports.forEach(handler::register);          // 遍历完之后执行processGroupImports()          handler.processGroupImports();        }      }      finally {        this.deferredImportSelectors = new ArrayList<>();      }    }复制代码
复制代码

遍历deferredImportSelectors集合,每个都会调用handler的#register()方法,这里将AutoConfigurationImportSelector的内部类AutoConfigurationGroup添加到groupings集合当中,并将对应的配置类添加到configurationClasses当中。遍历完deferredImportSelectors之后,调用handler.processGroupImports()

    public void processGroupImports() {      for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {        Predicate<String> exclusionFilter = grouping.getCandidateFilter();        grouping.getImports().forEach(entry -> {          ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());          try {            processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),                Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),                exclusionFilter, false);          }          catch (BeanDefinitionStoreException ex) {            throw ex;          }          catch (Throwable ex) {            throw new BeanDefinitionStoreException(                "Failed to process import candidates for configuration class [" +                    configurationClass.getMetadata().getClassName() + "]", ex);          }        });      }    }
复制代码

遍历之前放在groupings中的DeferredImportSelectorGrouping对象,调用#getImports()方法,该方法返回的是延迟注入的类名封装成的 Entry 结点的迭代器对象。

public Iterable<Group.Entry> getImports() {      for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {        this.group.process(deferredImport.getConfigurationClass().getMetadata(),            deferredImport.getImportSelector());      }      return this.group.selectImports();    }
复制代码

这里的this.group就是AutoConfigurationImportSelector的内部类AutoConfigurationGroup,遍历延迟注入类,调用#process()方法处理,该方法得到自动配置结点,将其添加到 autoConfigurationEntries 集合当中。再遍历自动配置结点的所有配置类的类名,添加到 entries 集合当中。

@Overridepublic void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {  Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,      () -> String.format("Only %s implementations are supported, got %s",          AutoConfigurationImportSelector.class.getSimpleName(),          deferredImportSelector.getClass().getName()));  // 获取自动配置类entry  AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)      .getAutoConfigurationEntry(annotationMetadata);  // 放入到autoConfigurationEntries集合中  this.autoConfigurationEntries.add(autoConfigurationEntry);  for (String importClassName : autoConfigurationEntry.getConfigurations()) {    this.entries.putIfAbsent(importClassName, annotationMetadata);  }}
复制代码

#getAutoConfigurationEntry()方法如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {   if (!isEnabled(annotationMetadata)) {      return EMPTY_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);   // 封装成AutoConfigurationEntry返回   return new AutoConfigurationEntry(configurations, exclusions);}
复制代码

#getCandidateConfigurations(annotationMetadata, attributes)方法如下:

  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),        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;  }
复制代码

SpringFactoriesLoader.loadFactoryNames()会调用#loadSpringFactories()方法:

这里就是加载META-INF/spring.factories目录下的自动配置类,使用的是 Java 提供 SPI(Service Provider Interface)扩展机制,不清楚该机制原理的可以看看之前总结的 SPI机制原理和使用,例如mybatis-plus的 start 的自动配置如下:

拿到所有自动配置类之后回到上面的#processGroupImports()grouping.getImports()获取到所有需要自动装配的类封装对象,接下来会进行一一遍历,调用#processImports()进行注入,至此Spring Boot就完成了自动装配。

三、自定义封装实现一个 starter

首先需要新增一个 maven 项目,按照Spring Boot官方建议命名格式为xxx-spring-boot-starter, 当然不遵从也是可以,我就没有遵从,这里我例举上面项目推荐中的基于mybatis-plus进行二次封装的一个框架 starter:plasticene-spring-boot-starter-mybatis,实现了分页插件,多租户插件集成,实体类公共字段的自动填充、复杂字段的类型处理,数据加密,以及条件构造流式查询等等功能封装。代码路径:github.com/plasticene/…。结构示意图如下:

starter 项目大概分两个包路径:autoconfigure存放自动配置类,core存放核心逻辑的封装,然后在resources建目录META-INF,写入配置文件spring.factories即可:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\  com.plasticene.boot.mybatis.autoconfigure.PlasticeneMybatisAutoConfiguration
复制代码

综上一个 starter 就封装好了,接下来我们只需要使用mvn install命令生成 pom 依赖文件进行发布,其他项目就可以引用了,如果是本地调试,不需要发布,因为install之后本地就有这个依赖包了。

四、总结

以上全部就是对Spring Boot自动配置原理的分析与讲解,其主要借助于@Import注解和SPI机制进行实现,搞清原理之后我们也手动封装了一个 starter 进行原理的理解与验证,完美诠释前面所述的实现原理。同时这也是面试高频考点,所以我们的花点心思搞懂它。


作者:shepherd111

链接:https://juejin.cn/post/7218857546277961785

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
Spring Boot自动配置原理详解和自定义封装实现starter_Java_做梦都在改BUG_InfoQ写作社区