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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 我们一直在强调`Spring Boot`能成为当下主流首选开发框架的主要原因在于其核心思想:**约定大于配置,自动配置,条件装配**。基于这些特性使得`Spring Boot`集成其他框架非常简单快捷

1.概述

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

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

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

@SpringBootApplication
public class CommonDemoApplication {
   
   

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

}

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

2.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
@Configuration
public @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容器中:

@Mapper
public 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集合当中。

@Override
public 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就完成了自动装配。

3.自定义封装实现一个starter

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

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

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.plasticene.boot.mybatis.autoconfigure.PlasticeneMybatisAutoConfiguration

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

4.总结

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

目录
相关文章
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
19天前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
98 0
|
15天前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
31 4
|
19天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
97 2
|
12天前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
26 0
|
5天前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
11天前
|
Java Spring 容器
springboot @RequiredArgsConstructor @Lazy解决循环依赖的原理
【10月更文挑战第15天】在Spring Boot应用中,循环依赖是一个常见问题,当两个或多个Bean相互依赖时,会导致Spring容器陷入死循环。本文通过比较@RequiredArgsConstructor和@Lazy注解,探讨它们解决循环依赖的原理和优缺点。@RequiredArgsConstructor通过构造函数注入依赖,使代码更简洁;@Lazy则通过延迟Bean的初始化,打破创建顺序依赖。两者各有优势,需根据具体场景选择合适的方法。
26 4
|
13天前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
80 1
|
2月前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
183 18
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
|
2月前
|
Java 应用服务中间件 API
Vertx高并发理论原理以及对比SpringBoot
Vertx 是一个基于 Netty 的响应式工具包,不同于传统框架如 Spring,它的侵入性较小,甚至可在 Spring Boot 中使用。响应式编程(Reactive Programming)基于事件模式,通过事件流触发任务执行,其核心在于事件流 Stream。相比多线程异步,响应式编程能以更少线程完成更多任务,减少内存消耗与上下文切换开销,提高 CPU 利用率。Vertx 适用于高并发系统,如 IM 系统、高性能中间件及需要较少服务器支持大规模 WEB 应用的场景。随着 JDK 21 引入协程,未来 Tomcat 也将优化支持更高并发,降低响应式框架的必要性。
Vertx高并发理论原理以及对比SpringBoot