前面我们讲了 SpringBoot 自动配置原理、@Conditional 系列条件注解,这些都是底层基础。今天我们把这些知识点落地,实战开发一个「私有 Starter」。
用过 SpringBoot 的都知道,引入一个 starter(比如 spring-boot-starter-web、spring-boot-starter-redis),就能实现开箱即用,不用手动配置。而自定义 Starter,本质就是「封装自己的公共组件/业务逻辑」,让其他项目能直接引入、快速复用,减少重复开发。
结合上两章的自动配置、条件注解知识,能更轻松理解 Starter 的底层逻辑,建议先回顾一下~
一、什么是 Starter?核心原理是什么?
在开发自定义 Starter 之前,我们先明确两个核心问题,避免盲目动手:
1. Starter 的本质
Starter 不是什么神秘的东西,它本质是一个「Maven 依赖包」,里面包含了:
-
• 核心业务代码(公共组件、工具类、业务逻辑等);
-
• 自动配置类(基于 @EnableAutoConfiguration,实现 Bean 自动注入);
-
• META-INF/spring.factories 文件(指定自动配置类,让 SpringBoot 能扫描到)。
简单说:Starter = 公共代码 + 自动配置 + 配置清单,目的是「封装复用、开箱即用」。
2. 自定义 Starter 的核心原理
自定义 Starter 的底层逻辑,就是我们上两章讲的内容,串联起来就是:
-
- 将公共组件/业务逻辑封装成 Bean,通过 @Configuration 配置类定义;
-
- 用 @Conditional 系列注解,实现 Bean 的按需加载(比如根据配置开关控制是否生效);
-
- 在 META-INF/spring.factories 文件中,注册自动配置类,让 SpringBoot 启动时能加载到;
-
- 其他项目引入该 Starter 依赖后,SpringBoot 自动加载配置类,注入 Bean,实现开箱即用。
3. 自定义 Starter 的命名规范(约定大于配置)
SpringBoot 官方有明确的命名规范,建议遵循(避免和官方 Starter 冲突,也更规范):
-
• 官方 Starter:spring-boot-starter-xxx(比如 spring-boot-starter-web、spring-boot-starter-redis);
-
• 自定义 Starter:xxx-spring-boot-starter(比如 mybatis-spring-boot-starter、aliyun-sms-spring-boot-starter)。
我们今天实战开发的 Starter,命名为:custom-sms-spring-boot-starter(自定义短信 Starter,实现短信发送功能的封装复用)。
二、从零开发 custom-sms-spring-boot-starter
本次实战目标:开发一个短信发送的私有 Starter,实现以下功能:
-
• 支持通过配置文件配置短信平台的 appId、appSecret;
-
• 提供短信发送接口,支持阿里云、腾讯云两种短信平台(可通过配置切换);
-
• 支持通过配置开关,开启/关闭短信功能;
-
• 其他项目引入该 Starter 后,直接注入 Bean 就能使用,无需额外配置。
开发环境:JDK8 + Maven + SpringBoot 2.7.x(兼容主流版本),步骤分5步,每一步都有详细代码和说明。
第一步:创建 Maven 项目(Starter 核心工程)
Starter 本身是一个 Maven 项目,不需要是 SpringBoot 项目(因为它是被其他 SpringBoot 项目引入的依赖),具体操作:
1. 新建 Maven 项目
IDE 中新建 Maven Project,GroupId 自定义(比如 com.example),ArtifactId 按规范命名为 custom-sms-spring-boot-starter,Packaging 为 jar(默认即可)。
2. 配置 pom.xml 依赖
核心依赖:SpringBoot 自动配置核心依赖(spring-boot-autoconfigure)、配置处理器(用于配置文件提示),无需引入 spring-boot-starter(避免依赖冲突)。
go
<?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 和 ArtifactId -->
<groupId>com.example</groupId>
<artifactId>custom-sms-spring-boot-starter</artifactId>
<version>1.0.0</version>
<name>custom-sms-spring-boot-starter</name>
<description>自定义短信发送 Starter</description>
<!-- 依赖管理 -->
<dependencies><!-- SpringBoot 自动配置核心依赖(必须) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.7.10</version>
<scope>provided</scope>
</dependency>
<!-- 配置处理器:用于在 application.yml 中提供配置提示(可选,但推荐) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.7.10</version>
<scope>provided</scope>
</dependency>
<!-- lombok(简化代码,可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies><!-- 打包配置(可选,确保打包后能被其他项目引入) -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<target>8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
说明:scope 设为 provided,是因为其他项目引入该 Starter 时,会自带 SpringBoot 相关依赖,避免重复引入导致版本冲突。
第二步:定义配置属性类(绑定配置文件)
用于绑定 application.yml 中的配置(比如短信平台的 appId、appSecret、平台类型、开启开关),通过 @ConfigurationProperties 注解实现。
go
package com.example.sms.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 短信配置属性类:绑定配置文件中的 sms 前缀配置
*/
@Data
@ConfigurationProperties(prefix = "sms") // 绑定配置文件中 sms 前缀的属性
public class SmsProperties {
/**
* 短信功能开启开关(默认关闭)
*/
private boolean enable = false;
/**
* 短信平台类型(aliyun:阿里云,tencent:腾讯云)
*/
private String platform;
/**
* 短信平台 appId
*/
private String appId;
/**
* 短信平台 appSecret
*/
private String appSecret;
/**
* 短信签名(可选,模拟场景)
*/
private String signName;
}
说明:@ConfigurationProperties(prefix = "sms") 表示该类会绑定配置文件中所有以「sms.」开头的属性,比如 sms.enable、sms.platform 等;添加 @Data 注解,自动生成 get/set 方法,简化代码。
第三步:开发核心业务逻辑(短信发送接口+实现)
封装短信发送的核心逻辑,提供统一接口,支持不同短信平台的实现(阿里云、腾讯云),便于扩展。
1. 定义短信发送接口
go
package com.example.sms.service;
/**
* 短信发送统一接口
*/
public interface SmsService {
/**
* 发送短信
* @param phone 手机号
* @param content 短信内容
* @return 发送结果(成功/失败)
*/
boolean sendSms(String phone, String content);
}
2. 实现阿里云短信发送
go
package com.example.sms.service.impl;
import com.example.sms.service.SmsService;
import lombok.Data;
/**
* 阿里云短信发送实现
*/
@Data
public class AliyunSmsServiceImpl implements SmsService {
// 从配置文件中注入的 appId 和 appSecret
private String appId;
private String appSecret;
private String signName;
@Override
public boolean sendSms(String phone, String content) {
// 模拟阿里云短信发送逻辑(实际开发中替换为阿里云官方 SDK 调用)
System.out.println("阿里云短信发送 -> appId:" + appId + ",appSecret:" + appSecret);
System.out.println("向手机号:" + phone + ",发送短信:" + content + ",签名:" + signName);
return true; // 模拟发送成功
}
}
3. 实现腾讯云短信发送
go
package com.example.sms.service.impl;
import com.example.sms.service.SmsService;
import lombok.Data;
/**
* 腾讯云短信发送实现
*/
@Data
public class TencentSmsServiceImpl implements SmsService {
private String appId;
private String appSecret;
private String signName;
@Override
public boolean sendSms(String phone, String content) {
// 模拟腾讯云短信发送逻辑(实际开发中替换为腾讯云官方 SDK 调用)
System.out.println("腾讯云短信发送 -> appId:" + appId + ",appSecret:" + appSecret);
System.out.println("向手机号:" + phone + ",发送短信:" + content + ",签名:" + signName);
return true;
}
}
第四步:编写自动配置类(实现 Bean 自动注入)
这是 Starter 的核心,通过自动配置类,根据配置文件的属性,自动创建 SmsService 实例,注入到 Spring 容器中,同时结合 @Conditional 系列注解,实现按需加载。
go
package com.example.sms.config;
import com.example.sms.service.SmsService;
import com.example.sms.service.impl.AliyunSmsServiceImpl;
import com.example.sms.service.impl.TencentSmsServiceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 短信 Starter 自动配置类
*/
@Configuration // 标记为配置类
@EnableConfigurationProperties(SmsProperties.class) // 启用配置属性绑定(让 SmsProperties 生效)
@ConditionalOnClass(SmsService.class) // 类路径中存在 SmsService 时生效(确保核心类存在)
@ConditionalOnProperty( // 根据配置文件中的 sms.enable 决定是否生效
prefix = "sms",
name = "enable",
havingValue = "true",
matchIfMissing = false
)
public class SmsAutoConfiguration {
// 注入配置属性类(从配置文件中读取的配置)
private final SmsProperties smsProperties;
// 构造方法注入(Spring 4.3+ 支持构造方法自动注入)
public SmsAutoConfiguration(SmsProperties smsProperties) {
this.smsProperties = smsProperties;
}
/**
* 阿里云短信 Bean:当平台类型为 aliyun 时创建
*/
@Bean
@ConditionalOnProperty(
prefix = "sms",
name = "platform",
havingValue = "aliyun"
)
@ConditionalOnMissingBean // 避免用户自定义 Bean 时被覆盖
public SmsService aliyunSmsService() {
AliyunSmsServiceImpl aliyunSms = new AliyunSmsServiceImpl();
// 将配置文件中的属性注入到 Bean 中
aliyunSms.setAppId(smsProperties.getAppId());
aliyunSms.setAppSecret(smsProperties.getAppSecret());
aliyunSms.setSignName(smsProperties.getSignName());
return aliyunSms;
}
/**
* 腾讯云短信 Bean:当平台类型为 tencent 时创建
*/
@Bean
@ConditionalOnProperty(
prefix = "sms",
name = "platform",
havingValue = "tencent"
)
@ConditionalOnMissingBean
public SmsService tencentSmsService() {
TencentSmsServiceImpl tencentSms = new TencentSmsServiceImpl();
tencentSms.setAppId(smsProperties.getAppId());
tencentSms.setAppSecret(smsProperties.getAppSecret());
tencentSms.setSignName(smsProperties.getSignName());
return tencentSms;
}
}
关键注解解读
-
•
@EnableConfigurationProperties(SmsProperties.class):启用配置属性绑定,让 Spring 能识别 SmsProperties 类,将配置文件中的属性注入到该类中; -
•
@ConditionalOnClass(SmsService.class):确保类路径中存在 SmsService 接口(核心业务类),避免依赖缺失导致报错; -
•
@ConditionalOnProperty:控制自动配置的开关(sms.enable=true 才生效),同时根据 sms.platform 切换不同的短信平台实现; -
•
@ConditionalOnMissingBean:允许用户自定义 SmsService 实现,覆盖 Starter 中的默认实现,符合 SpringBoot 「约定大于配置」的理念。
第五步:创建 META-INF/spring.factories 文件(注册自动配置类)
这是最关键的一步------SpringBoot 启动时,会通过 SpringFactoriesLoader 读取 META-INF/spring.factories 文件,找到自动配置类并加载。如果没有这个文件,自动配置类不会被 Spring 扫描到,Starter 就会失效。
1. 创建目录
在 src/main/resources 目录下,新建 META-INF 文件夹(注意大写),然后在 META-INF 文件夹下,新建 spring.factories 文件。
2. 配置自动配置类
在 spring.factories 文件中,添加以下内容,指定我们编写的自动配置类:
go
# SpringBoot 自动配置类清单
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.sms.config.SmsAutoConfiguration
说明:
-
• key 固定为
org.springframework.boot.autoconfigure.EnableAutoConfiguration(SpringBoot 自动配置的核心 key); -
• value 为我们编写的自动配置类的全类名(com.example.sms.config.SmsAutoConfiguration);
-
• 如果有多个自动配置类,用逗号分隔,换行用反斜杠「\」连接。
第六步:打包 Starter(供其他项目引入)
Starter 开发完成后,需要打包成 jar 包,才能被其他 SpringBoot 项目引入使用,打包步骤:
-
- 打开 IDEA 的 Maven 面板,找到当前 Starter 项目;
-
- 点击「clean」(清理旧的打包文件),再点击「package」(打包);
-
- 打包完成后,jar 包会生成在 target 目录下(比如 custom-sms-spring-boot-starter-1.0.0.jar)。
补充:如果需要在公司内部共享该 Starter,可将其上传到公司的 Maven 私有仓库(如 Nexus),其他项目通过 pom.xml 引入即可;如果只是本地测试,可将 jar 包安装到本地 Maven 仓库(点击 Maven 面板的「install」)。
三、测试验证:新建 SpringBoot 项目,引入自定义 Starter
开发完 Starter 后,必须测试验证,确保能正常使用,步骤如下:
1. 新建 SpringBoot 测试项目
新建一个 SpringBoot 项目(比如 test-sms-starter),引入 web 依赖(方便测试接口)。
2. 引入自定义 Starter 依赖
在测试项目的 pom.xml 中,引入我们打包好的 Starter 依赖(本地测试需先 install 到本地仓库):
go
<!-- 引入自定义短信 Starter -->
<dependency>
<groupId>com.example</groupId>
<artifactId>custom-sms-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
<!-- web 依赖(用于测试接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3. 配置 application.yml
在测试项目的 application.yml 中,添加短信相关配置,开启短信功能,指定阿里云平台:
go
server:
port: 8080
# 自定义短信 Starter 配置
sms:
enable: true # 开启短信功能
platform: aliyun # 选择阿里云短信平台
app-id: 123456 # 模拟 appId
app-secret: abcdef123456 # 模拟 appSecret
sign-name: 我的测试签名 # 短信签名
4. 编写测试接口
创建一个测试 Controller,注入 SmsService,调用短信发送方法:
go
package com.example.test.controller;
import com.example.sms.service.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SmsTestController {
// 直接注入自定义 Starter 中的 SmsService(自动配置,无需手动创建)
@Autowired
private SmsService smsService;
@GetMapping("/sendSms")
public String sendSms(@RequestParam String phone, @RequestParam String content) {
boolean result = smsService.sendSms(phone, content);
return result ? "短信发送成功" : "短信发送失败";
}
}
5. 启动项目,测试接口
-
- 启动测试项目,SpringBoot 会自动加载 SmsAutoConfiguration 配置类,创建 AliyunSmsServiceImpl Bean;
-
- 控制台会打印阿里云短信发送的相关信息,接口返回「短信发送成功」,说明 Starter 正常生效。
额外测试:切换短信平台
将 application.yml 中的 sms.platform 改为 tencent,重启项目,再次访问接口,控制台会打印腾讯云短信发送信息,说明条件注解生效,能根据配置切换 Bean。
四、注意事项
开发自定义 Starter 时,有很多细节需要注意,避免出现依赖冲突、配置不生效等问题,下面这些最佳实践,一定要遵循:
1. 依赖管理:避免引入多余依赖
Starter 是公共依赖,尽量只引入核心依赖(如 spring-boot-autoconfigure),避免引入 web、redis 等具体依赖------如果需要依赖其他组件,建议用 @ConditionalOnClass 判断,让用户自行引入相关依赖,避免依赖冲突。
2. 配置属性:提供默认值,增加容错性
在 SmsProperties 中,给非必填属性提供默认值(比如 sms.enable 默认 false),避免用户未配置时出现空指针异常;同时,通过 @ConfigurationProperties 的 validate 功能,对必填属性进行校验(比如 appId、appSecret 不能为空)。
3. 条件注解:合理使用,避免无效加载
结合 @ConditionalOnProperty、@ConditionalOnClass 等注解,实现按需加载,避免无用 Bean 占用资源;比如只有开启 sms.enable=true 时,才加载短信相关 Bean。
4. 命名规范:遵循官方约定
严格按照「xxx-spring-boot-starter」的命名规范,避免和官方 Starter 冲突;同时,配置属性的前缀(如 sms)尽量唯一,避免和其他 Starter 的配置冲突。
5. 文档说明:添加 README 文件
给 Starter 项目添加 README 文件,说明 Starter 的功能、配置属性、使用方法、依赖版本要求等,方便其他开发者使用。
五、面试高频考点(自定义 Starter 必背)
自定义 Starter 是 SpringBoot 面试的高频考点:
自定义 Starter 的核心组成部分有哪些?
答:核心有3部分:① 配置属性类(@ConfigurationProperties):绑定配置文件中的属性;② 自动配置类(@Configuration + 条件注解):实现 Bean 自动注入;③ META-INF/spring.factories 文件:注册自动配置类,让 SpringBoot 能扫描到。
SpringBoot 如何识别自定义 Starter 中的自动配置类?
答:SpringBoot 启动时,会通过 SpringFactoriesLoader 工具类,读取 classpath 下 META-INF/spring.factories 文件,该文件中配置了自动配置类的全类名,SpringBoot 会加载这些自动配置类,结合条件注解筛选后,注入对应的 Bean。
如何实现 Starter 的按需加载?
答:通过 @Conditional 系列注解实现按需加载,比如:① @ConditionalOnProperty:根据配置文件的开关控制是否生效;② @ConditionalOnClass:根据是否引入依赖控制是否生效;③ @ConditionalOnMissingBean:允许用户自定义 Bean 覆盖默认实现。
自定义 Starter 和官方 Starter 的区别是什么?
答:① 命名规范不同:官方 Starter 是 spring-boot-starter-xxx,自定义 Starter 是 xxx-spring-boot-starter;② 功能不同:官方 Starter 是通用组件(如 web、redis),自定义 Starter 是业务相关的公共组件(如短信、支付);③ 维护主体不同:官方 Starter 由 Spring 团队维护,自定义 Starter 由开发者自己维护。
六、总结
- •
-
- 自定义 Starter 本质是「封装公共组件的 Maven 依赖包」,核心是自动配置 + 配置清单;
-
•
-
-
- 开发步骤:创建 Maven 项目 → 定义配置属性类 → 开发核心业务逻辑 → 编写自动配置类 → 配置 spring.factories → 打包;
-
-
•
-
-
- 关键注解:@ConfigurationProperties(绑定配置)、@Configuration(配置类)、@EnableConfigurationProperties(启用配置绑定)、@Conditional 系列(按需加载);
-
-
•
-
-
- 核心价值:实现公共组件复用,减少重复开发,让其他项目能开箱即用;
-
-
•
-
-
- 避坑关键:合理管理依赖、提供默认配置、遵循命名规范、使用条件注解实现按需加载。
到这里,我们就从零开发完成了一个私有 Starter,从底层原理到实战操作,每一步都清晰可落地。其实自定义 Starter 并不复杂,核心就是把我们之前学的自动配置、条件注解知识,串联起来,应用到实际开发中。
以后工作中,遇到需要复用的公共组件(比如短信、支付、日志、权限等),就可以按照这个模板,开发自己的 Starter,提升开发效率,也能体现你的技术功底。
-