从零手写一个 Spring Boot Starter:深入理解自动装配原理

从零手写一个 Spring Boot Starter:深入理解自动装配原理

 次点击
26 分钟阅读

做 SpringBoot 项目开发的同学都知道,平时项目中会用到很多第三方的 starter 依赖,比如 MyBatisPlus 的依赖,mybatis-plus-spring-boot3-starter。有没有好奇过这个依赖是怎么开发出来的,为什么引入后就可以使用很多依赖里的功能?

本文将手把手带你写一个输出一个类似 MyBatisPlus 启动后控制台输出一个 Banner 的 SpringBootStarter

本次开发的源码已同步GitHub仓库 BannerStarter

场景启动器 Starter

一个最简单的场景启动器一般包含已下几个部分

组件扮演角色功能描述
BannerService业务组件●执行输出 Banner 的业务逻辑功能
BannerProperties属性组件●封装配置文件属性值,读取配置文件中是否开启输出 Banner 的功能
BannerAutoConfiguration自动配置类●主要任务:注册属性组件
●次要任务:确保业务组件放入IoC容器,调用方可以直接注入业务组件并调用组件执行逻辑
AutoConfiguration.imports声明自动配置类让 SpringBoot 指导要加载哪个自动配置类

自顶向下开发

项目依赖

SpringBoot 官方的启动器一般 artifactId 为 spring-boot-starter-xxx,例如:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>4.0.5</version>
    <scope>compile</scope>
</dependency>

如果是三方的 starter 的话,通常 artifactId 为 xxx-spring-boot-starter

所以我们的项目 artifactId 命名为 banner-spring-boot-starter

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>top.dhbxs.demo</groupId>
    <artifactId>banner-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- springboot 自动配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>3.5.13</version>
        </dependency>

        <!-- springboot 配置处理器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>2.0.4.RELEASE</version>
            <!-- 添加optional属性,不传递此依赖,此依赖只在编译时期生成配置元数据 -->
            <optional>true</optional>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.5.13</version>
            </plugin>
        </plugins>
    </build>

</project>

AutoConfiguration.imports 文件

首先要有一个 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件用于声明自动配置类,这个文件的文件名不能改,SpringBoot 启动时会解析这个文件,然后自动加载里面配置好的类,SpringBoot 官方的 starter 有 142 个自动配置类。如果项目引入了我们当前开发的这个 starter,那 SpringBoot 启动时就会加载自身 142 个再加上我们配置好的类。

在 resources 目录下新建 META-INF/spring 文件夹,然后在这个文件夹下新建名为 org.springframework.boot.autoconfigure.AutoConfiguration.imports 的文件,内容如下:

top.dhbxs.demo.config.BannerAutoConfiguration

这里的内容就是我们 BannerStarter 里的自动装配类的 全限定类名。

然后顺手在 resources 目录下新建个 dhbxs 的文件夹,存放我们要在控制台打印的 banner 字符,命名为 banner.txt

      ██   ██        ██
     ░██  ░██       ░██
     ░██  ░██       ░██        ██   ██    ██████
  ██████  ░██████   ░██████   ░░██ ██    ██░░░░
 ██░░░██  ░██░░░██  ░██░░░██   ░░███    ░░█████
░██  ░██  ░██  ░██  ░██  ░██    ██░██    ░░░░░██
░░██████  ░██  ░██  ░██████    ██ ░░██   ██████
 ░░░░░░   ░░   ░░   ░░░░░     ░░   ░░   ░░░░░░
:: My Framework ::  (v1.0.0)

BannerAutoConfiguration 类

在 java 目录下,新建 config 包,在该包里新建 BannerAutoConfiguration.java 类。

这个类是 starter 自动装配执行的第一个类,类上方有两个比较特殊的注解:

  1. @ConditionalOnClass(BannerService.class)

这个注解主要是在执行 BannerAutoConfiguration 类之前,检查类路径下有没有 BannerService 这个类,如果有,则把当前 BannerAutoConfiguration 这个类加入 IoC 容器。

主要是防止在使用我们框架的项目中,如果没有完整下载到我们的 starter,依赖缺失导致报 ClassNotFoundException异常,如果依赖缺失,也就不进行自动装配了。

  1. @EnableConfigurationProperties(BannerProperties.class)

这个注解主要是加载属性组件,读取用户在配置文件中配置的数据,把 BannerProperties 也注册为 SpringBean

该类中把业务逻辑类 BannerService 也 new 出来,然后创建为一个 SpringBean,方便框架调用方直接可以注入该 Bean 调用业务逻辑。

package top.dhbxs.demo.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.dhbxs.demo.properties.BannerProperties;
import top.dhbxs.demo.service.BannerService;

/**
 * Banner 自动配置类
 *
 * @author dhbxs
 * @since 2026/4/9
 */
@Configuration
// 如果当前类路径下有 BannerService 这个类,就把当前类加入 IoC 容器
@ConditionalOnClass(BannerService.class)
// 注册属性组件,SpringBoot 会读取用户在配置文件中指定的值,注入属性组件
@EnableConfigurationProperties(BannerProperties.class)
public class BannerAutoConfiguration {

    private final BannerProperties bannerProperties;

    public BannerAutoConfiguration(BannerProperties bannerProperties) {
        this.bannerProperties = bannerProperties;
    }

    @Bean
    // 条件注入 当 IoC 容器中没有这个 bean 时,才把返回对象注入到 IoC 容器中,如果有,则不注入
    @ConditionalOnMissingBean
    public BannerService bannerService() {
        // 创建BannerService时,把用户配置的是否启用也传进去
        return new BannerService(bannerProperties.getEnable());
    }
}

BannerProperties 类

这个类就是和我们平时自己写的从 application.yml 中读取配置信息的类一样,使用@ConfigurationProperties(prefix = "banner") 注解定义已 banner 开头的配置属性

新建 properties 包,放在该包下

package top.dhbxs.demo.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * Banner 是否启用配置类
 *
 * @author dhbxs
 * @since 2026/4/9
 */
@Component
@ConfigurationProperties(prefix = "banner")
public class BannerProperties {
    /**
     * 是否启用 Banner
     */
    private Boolean enable = true;

    public Boolean getEnable() {
        return enable;
    }

    public void setEnable(Boolean enable) {
        this.enable = enable;
    }
}

BannerService 类

这个类是我们打印 Banner 的核心业务逻辑类,在构造器中调用该类中的方法实现在启动 SpringBoot 项目时,自动打印 Banner。

主要实现读取我们定义的 dhbxs/banner.txt 文件,然后将里面的内容打印到控制台。

package top.dhbxs.demo.service;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;

/**
 * 打印 Banner 业务类
 *
 * @author dhbxs
 * @since 2026/4/9
 */
public class BannerService {

    // 是否启用
    private final Boolean enable;

    public BannerService(Boolean enable) {
        this.enable = enable;
        this.print();
    }

    /**
     * 从classpath读取banner.txt并打印到控制台
     *
     * @param bannerPath classpath下的路径,如 "banner.txt" 或 "static/banner.txt"
     */
    public void print(String bannerPath) {
        try (InputStream is = BannerService.class.getClassLoader().getResourceAsStream(bannerPath)) {
            if (is == null) {
                System.out.println("Banner not found: " + bannerPath);
                return;
            }
            String banner = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))
                    .lines()
                    .collect(Collectors.joining("\n"));
            System.out.println(banner);
        } catch (Exception e) {
            System.out.println("Failed to print banner: " + e.getMessage());
        }
    }

    /**
     * 使用默认路径 banner.txt
     */
    public void print() {
        if (Boolean.TRUE.equals(this.enable)) {
            print("dhbxs/banner.txt");
        }
    }
}

打包并给其他项目使用

使用 Maven package 将项目打包后,可以用 install 命令安装到本地 Maven 仓库,然后其他项目中使用 Maven 坐标即可将依赖引入项目

<dependency>
  <groupId>top.dhbxs.demo</groupId>
  <artifactId>banner-spring-boot-starter</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

如果一切正常,启动 SpringBoot 项目后应该可以看到 SpringBoot 的官方 Banner,然后便可以看到我们 banner-spring-boot-starter 依赖所打印的 Banner,并且可以在 application.yml 中灵活配置是否打印 Banner

banner:
  enable: true
© 本文著作权归作者所有,未经许可不得转载使用。