Spring Boot 3.x 开发 Starter 快速上手体验,通过实践理解自动装配原理

本篇文章讲述 Spring Boot 自动装配的概念和实战, 开发一个 starter, 穿插自动装配的知识点理解。

什么是自动装配?

传统 Spring 里,我们需要在 applicationContext.xml 或者配置类里,手动写 一堆 <bean>@Bean,告诉 Spring 要创建哪些对象(Bean)。

Spring Boot 的目标是"约定大于配置",让开发者尽量少写配置,所以它引入了 自动装配,

定义: 自动装配 = Spring Boot 根据当前 classpath 里的依赖、配置文件和条件判断,自动帮你注册合适的 Bean 到容器中。

你只要引了依赖,Spring Boot 就猜到你要用啥,并帮你准备好对应的 Bean。

接下来实战上手自动装配,再深入讲原理。

需求分析

开发一个 excel 文档导出,要求在查询接口上使用注解,便捷导出 excel 文档。

方案

我们开发一个starter , starter 用到了自动装配功能 , 而且 starter 已经做了相关的配置和需要的依赖,调用方引入我们的stater, 通过一个注解就能实现 excel 文档导出,符合需求中要的便捷性。

环境说明

  • spring boot 3.5.6
  • java 21
  • Spring web
  • lombok
  • 第三方依赖 Apache POI (操作 Excel)

pom 依赖:

注意打 jar 包的插件,不能打成可执行 jar,而是普通 jar 包,否则其它模块无法引用 jar 包。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.demoexcel</groupId>
    <artifactId>excel-stater</artifactId>
    <version>1.0.0</version>
    <name>ExcelExportAutoConfig</name>
    <description>ExcelExportAutoConfig</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>21</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- Spring Boot Web MVC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.36</version>
            <optional>true</optional>
        </dependency>

        <!-- Spring Boot AOP -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--Apache POI 来操作 Excel-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <skip>true</skip> <!-- 不生成可执行 jar -->
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

SpringBoot 实现 excel 导出功能

我们先开发导出功能,再加入自动装配,然后可以生成一个starter 最终让别的项目引入,便可以从实践得出 spring boot 自动装配与 starter 的效果。

项目名称:my-excel-starter

初始化后端项目,依赖安装环境说明配置。

application.yml 配置文件啥也不用写,追求快速上手。

开发流程:

  1. 编写一个注解 @ExcelExport
  2. 编写一个 AOP 切面, 切面内先让切入点执行方式,得到方法的返回值,使用 Apache POI 操作 excel 导出。
  3. controller 开发一个 exportUser 接口(理解成一个list查询接口),接口内模拟两条查询到的假数据。使用 @ExcelExport 便能实现导出功能。

功能开发好了我们再加入自动装配。

  1. 注解开发
java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExcelExport {
    String filename() default "export.xlsx";
}
  1. 切面开发
java 复制代码
/**
 *  AOP , 导出Excel 文件
 */

@Aspect
@Component
public class ExcelExportAspect {

    @SneakyThrows
    @Around("@annotation(excelExport)")
    public Object exportExcel(ProceedingJoinPoint pjp, ExcelExport excelExport) {
        Object result = pjp.proceed();

        if (result instanceof List<?> list && !list.isEmpty() && list.get(0) instanceof Map<?,?> row) {
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder
                    .currentRequestAttributes())
                    .getResponse();

            Workbook workbook = new XSSFWorkbook();
            Sheet sheet = workbook.createSheet("Sheet1");

            // 表头
            Row header = sheet.createRow(0);
            int col = 0;
            for (Object key : row.keySet()) {
                header.createCell(col++).setCellValue(key.toString());
            }

            // 数据
            int rowNum = 1;
            for (Object obj : list) {
                Map<?, ?> map = (Map<?, ?>) obj;
                Row dataRow = sheet.createRow(rowNum++);
                int c = 0;
                for (Object value : map.values()) {
                    dataRow.createCell(c++).setCellValue(value != null ? value.toString() : "");
                }
            }

            // 写入 HttpServletResponse
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-Disposition",
                    "attachment; filename=" + URLEncoder.encode(excelExport.filename(), "UTF-8"));

            workbook.write(response.getOutputStream());
            workbook.close();
            return null;
        }

        return result;
    }
}
  1. controller 接口测试导出功能
java 复制代码
@RestController
public class DemoController {

    @ExcelExport(filename = "users.xlsx")
    @GetMapping("/export")
    public List<Map<String, Object>> exportUsers() {
        List<Map<String, Object>> list = new ArrayList<>();
        list.add(Map.of("id", 1, "name", "Alice", "age", 20));
        list.add(Map.of("id", 2, "name", "Bob", "age", 25));
        return list;
    }
}

测试,访问 localhost:8080\export 自动下载 excel 文件。

功能开发完成,我们把这个项目当成一个工具,提供给别的项目使用,为了便于其它项目使用,实现自动装配

自动装配

工作原理

名词解释:

@Bean (标记方法): 声明一个工厂方法,返回值对象会注册到 Spring 容器,成为一个 Bean。

@Configuration : 声明这是一个配置类,里面的 @Bean 方法会被 Spring 识别,用来定义 Bean。

  • @Configuration 本身也是 @Component,会被 Spring 扫描并注册为 Bean。
  • Spring 会用 CGLIB 代理增强这个类。
  • 调用 @Bean 方法时,会拦截调用,确保返回的是容器里已有的单例,而不是新对象。

@Component : 如果与 @Bean 搭配 , Spring 不会用代理,每次调用Bean的方法都是 new 一个新的对象。

如何实现自动装配?

Spring Boot 的自动装配流程大概是这样:

当你启动一个 Spring Boot 应用时(@SpringBootApplication)。

  1. @SpringBootApplication 注解内部有**@EnableAutoConfiguration**
  2. 加载自动配置类
  • @EnableAutoConfiguration@Import(AutoConfigurationImportSelector.class)

  • AutoConfigurationImportSelector 会去扫描配置文件:

  • Spring Boot 2.x → META-INF/spring.factories

  • Spring Boot 3.x → META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

  • 找到所有的 xxxAutoConfiguration 类(比如 DataSourceAutoConfigurationWebMvcAutoConfiguration)。

  1. 注册配置类
  • 这些自动配置类(其实就是 @Configuration 类)被 注册到 Spring 容器,但还没有立即实例化 Bean。
  1. 条件判断(筛选)
  • 每个自动配置类、或者其中的 Bean 方法,通常带有 @Conditional 系列注解:

  • @ConditionalOnClass:classpath 里有某个类才生效

  • @ConditionalOnMissingBean:用户没有自定义 Bean 才生效

  • @ConditionalOnProperty:配置文件里设置了某个属性才生效

  • Spring 会根据这些条件,决定哪些配置类/Bean 要真正装配。

  1. 注册 Bean 定义
  • BeanDefinitionRegistry (Bean的总入口) 负责把各种来源的 **Bean 定义信息转成 BeanDefinition ,**最后存到一个映射表里(BeanDefinitionMap)。
  • BeanFactory 负责根据 BeanDefinition 实例化对象。
  1. 容器刷新(refresh) → 实例化 Bean
  • Spring 容器开始创建单例 Bean,执行依赖注入。
  • 这时,自动配置的 Bean 就和你手动写的 Bean 一起被实例化,进入 Spring 上下文。

可以看出自动配置很灵活,按条件注册Bean 。

注意:spring 3.x 使用 META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 作为自动装配的配置文件,区别于 spring 2.x 使用的 META-INF/spring.factories

以上可知,想要实现自动装配核心要编写:

  • 编写自动配置类 xxxxAutoConfiguration
  • 配置文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
开发实现
  1. 编写自动配置类 ExcelExportAutoConfiguration , 打上条件注解@ConditionalOnMissingBean(ExcelExportAspect.class) , 如果不存在ExcelExportAspect.class 才会加载这个配置类。
java 复制代码
@Configuration
@ConditionalOnMissingBean(ExcelExportAspect.class)
public class ExcelExportAutoConfiguration {

    @Bean
    public ExcelEx
    portAspect excelExportAspect() {
        return new ExcelExportAspect();
    }
}
  1. AutoConfiguration.imports 放入你想要自动装配的配置类
java 复制代码
com.demoexcel.excelstater.config.ExcelExportAutoConfiguration

目录结构如下

生成 stater

经过以上步骤,已经开发好了一个 stater, 接下来仅需 maven install 打出一个 jar 包,在其它项目使用测试效果。

双击以后,本地maven仓库会有一个依赖包,可在其它项目中引入,依赖包的坐标是本项目的坐标:

确认本地maven仓库有这个依赖:C:\Users\Administrator.m2\repository\com\demoexcel

引入 starter

刚刚生成了一个 starter 我们自己测试看效果。

  1. 创建一个Spring boot项目,项目名称: example-excel,基本 web 依赖。

  2. 引入 starter,新项目直接使用注解,立刻享受开箱即用,什么配置都不用改。

  3. 写一个controller , 打上注解 @ExcelExport(filename = "users.xlsx")

java 复制代码
@RestController
public class DemoController {

    @ExcelExport(filename = "users.xlsx")
    @GetMapping("/export")
    public List<Map<String, Object>> exportUsers() {

        List<Map<String, Object>> list = new ArrayList<>();
        list.add(Map.of("id", 1, "name", "Alice"));
        list.add(Map.of("id", 2, "name", "Bob"));
        return list;
    }
}

启动新项目,访问接口,下载 excel 功能可用。

理解自动装配作用

为了帮助理解自动装配的作用,最后作出一点说明,如果在 stater项目中没有编写 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置,那么新项目引入starter 依赖包需要在启动类添加包扫描的配置,因为spring 默认仅扫描启动类同级目录的包。自动装配中还写了一个配置类,能够确保Bean的实例化。

自动装配的价值在于实现了stater 的开箱即用,引入依赖不用实例化 bean 、包扫描等配置。

自动装配的本质

Spring Boot 自动装配就是通过条件化配置 ,把需要的 Bean 定义提前写好,然后在应用启动时根据运行环境(类路径、配置文件、已有 Bean)去"有选择地注册这些 Bean"。

自动装配Bean 和普通Bean注册相似,不过是多了一个条件化配置,减少了配置冲突。

关键组件

  • Configuration Class Parser(配置类解析器)

  • 负责解析 @Configuration@Bean,由 ConfigurationClassPostProcessor 驱动。

  • AutoConfiguration Import Selector(选择器)

  • 负责把**所有候选的自动配置类(spring.factories,AutoConfiguration.imports)**导入。

  • ConditionEvaluator(条件评估器)

  • 评估 @ConditionalXXX 注解,决定配置类是否生效。

  • BeanDefinitionRegistry(Bean 定义注册器)

  • 真正向容器注册 Bean 定义。

  • 负责根据 BeanDefinition 实例化对象

  • ApplicationContext(上下文容器)

  • Bean 创建、依赖注入、生命周期管理都发生在这里。

相关推荐
悟能不能悟3 小时前
什么是反应式编程
java
南方者3 小时前
【JAVA】【BUG】Java 开发中常见问题的具体示例,结合代码片段说明问题场景及原因
java·后端·debug
没有bug.的程序员3 小时前
MySQL 配置调优参数:从基础到生产级优化指南
java·数据库·mysql·优化·mysql配置调优
寻月隐君3 小时前
Rust 泛型编程基石:AsRef 和 AsMut 的核心作用与实战应用
后端·github
画船听雨眠aa3 小时前
Java8新特性——Stream API常见操作
java
Java水解3 小时前
100道互联网大厂面试题+答案
java·后端·面试
optimistic_chen3 小时前
【Java EE进阶 --- SpringBoot】Mybatis操作数据库(基础)
数据库·经验分享·spring boot·笔记·spring·java-ee·mybatis
用户8356290780514 小时前
使用Python自动化移除Excel公式,保留纯净数值
后端·python
nlog3n4 小时前
分布式计数器系统完整解决方案
java·分布式