Spring Boot Starters 开发详解
一、知识概述
Spring Boot Starter 是 Spring Boot 的核心特性之一,它通过依赖管理和自动配置简化了项目的初始搭建和开发过程。一个 Starter 通常是一个空的 JAR 包,用于管理一组依赖项的版本和自动配置。
Starter 的核心价值:
- 依赖管理:统一管理相关依赖版本
- 自动配置:自动配置相关 Bean
- 开箱即用:引入即可使用
- 约定优于配置:提供合理的默认值
理解 Starter 的开发方式,能够帮助开发者封装通用功能,提高团队开发效率。
二、知识点详细讲解
2.1 Starter 命名规范
官方 Starter 命名
spring-boot-starter-{name}
例如:
- spring-boot-starter-web
- spring-boot-starter-data-jpa
- spring-boot-starter-security
第三方 Starter 命名
{name}-spring-boot-starter
例如:
- mybatis-spring-boot-starter
- druid-spring-boot-starter
- pagehelper-spring-boot-starter
2.2 Starter 项目结构
标准 Starter 项目结构:
my-spring-boot-starter/
├── src/main/java/
│ └── com/example/
│ ├── autoconfigure/
│ │ ├── MyAutoConfiguration.java
│ │ ├── MyProperties.java
│ │ └── MyCondition.java
│ └── MyService.java
├── src/main/resources/
│ └── META-INF/
│ ├── spring.factories
│ └── spring-configuration-metadata.json
└── pom.xml
2.3 核心文件说明
pom.xml
<project>
<dependencies>
<!-- 自动配置依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 配置处理器(可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 实际功能依赖 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-core</artifactId>
</dependency>
</dependencies>
</project>
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.MyAutoConfiguration
spring-configuration-metadata.json
{
"groups": [
{
"name": "my.service",
"type": "com.example.autoconfigure.MyProperties",
"sourceType": "com.example.autoconfigure.MyProperties"
}
],
"properties": [
{
"name": "my.service.enabled",
"type": "java.lang.Boolean",
"description": "是否启用服务",
"defaultValue": true
}
]
}
2.4 自动配置类设计
自动配置类应该遵循以下原则:
- 条件化加载:使用 @Conditional 注解
- 可覆盖:使用 @ConditionalOnMissingBean
- 属性绑定:使用 @ConfigurationProperties
- 文档完整:添加 Javadoc 注释
2.5 Starter 分类
-
应用 Starter:面向特定应用场景
- spring-boot-starter-web
- spring-boot-starter-batch
-
技术 Starter:面向特定技术
- spring-boot-starter-data-redis
- spring-boot-starter-amqp
-
生产 Starter:面向生产环境
- spring-boot-starter-actuator
三、代码示例
3.1 简单 Starter 开发
项目结构
simple-spring-boot-starter/
├── src/main/java/
│ └── com/example/
│ ├── autoconfigure/
│ │ ├── SimpleAutoConfiguration.java
│ │ └── SimpleProperties.java
│ └── SimpleService.java
├── src/main/resources/
│ └── META-INF/
│ └── spring.factories
└── pom.xml
pom.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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>simple-spring-boot-starter</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>Simple Spring Boot Starter</name>
<description>A simple Spring Boot Starter example</description>
<properties>
<java.version>17</java.version>
<spring-boot.version>3.2.0</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot 自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- 配置处理器(生成元数据) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Lombok(可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
SimpleService.java
package com.example;
public class SimpleService {
private final String prefix;
private final String suffix;
public SimpleService(String prefix, String suffix) {
this.prefix = prefix;
this.suffix = suffix;
}
public String wrap(String message) {
return prefix + message + suffix;
}
public String getPrefix() {
return prefix;
}
public String getSuffix() {
return suffix;
}
}
SimpleProperties.java
package com.example.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "simple.service")
public class SimpleProperties {
/**
* 是否启用服务
*/
private boolean enabled = true;
/**
* 前缀
*/
private String prefix = "[";
/**
* 后缀
*/
private String suffix = "]";
// getter/setter
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getPrefix() { return prefix; }
public void setPrefix(String prefix) { this.prefix = prefix; }
public String getSuffix() { return suffix; }
public void setSuffix(String suffix) { this.suffix = suffix; }
}
SimpleAutoConfiguration.java
package com.example.autoconfigure;
import com.example.SimpleService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(SimpleService.class)
@EnableConfigurationProperties(SimpleProperties.class)
public class SimpleAutoConfiguration {
private final SimpleProperties properties;
public SimpleAutoConfiguration(SimpleProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "simple.service", name = "enabled",
havingValue = "true", matchIfMissing = true)
public SimpleService simpleService() {
return new SimpleService(properties.getPrefix(), properties.getSuffix());
}
}
spring.factories
org.springframework.boot.autoconfigure.AutoConfiguration.ImportCandidate=\
com.example.autoconfigure.SimpleAutoConfiguration
注:Spring Boot 3.x 推荐使用
AutoConfiguration.ImportCandidate替代EnableAutoConfiguration。
使用 Starter
<!-- 在项目中引入 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>simple-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
# application.yml
simple:
service:
enabled: true
prefix: "<<"
suffix: ">>"
@Service
public class MyService {
private final SimpleService simpleService;
public MyService(SimpleService simpleService) {
this.simpleService = simpleService;
}
public void doSomething() {
String result = simpleService.wrap("Hello World");
System.out.println(result); // <<Hello World>>
}
}
3.2 带 Actuator 的 Starter
项目结构
monitoring-spring-boot-starter/
├── src/main/java/
│ └── com/example/
│ ├── autoconfigure/
│ │ ├── MonitoringAutoConfiguration.java
│ │ └── MonitoringProperties.java
│ ├── endpoint/
│ │ └── CustomEndpoint.java
│ ├── health/
│ │ └── CustomHealthIndicator.java
│ └── metrics/
│ └── MetricsService.java
└── src/main/resources/
└── META-INF/
└── spring.factories
MonitoringAutoConfiguration.java
package com.example.autoconfigure;
import com.example.endpoint.CustomEndpoint;
import com.example.health.CustomHealthIndicator;
import com.example.metrics.MetricsService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass({MetricsService.class, CustomHealthIndicator.class})
@EnableConfigurationProperties(MonitoringProperties.class)
public class MonitoringAutoConfiguration {
private final MonitoringProperties properties;
public MonitoringAutoConfiguration(MonitoringProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "monitoring.enabled", name = "metrics",
havingValue = "true", matchIfMissing = true)
public MetricsService metricsService() {
return new MetricsService(properties.getMetricsPrefix());
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "monitoring.enabled", name = "health",
havingValue = "true", matchIfMissing = true)
public CustomHealthIndicator customHealthIndicator() {
return new CustomHealthIndicator(properties.getHealthCheckUrl());
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "monitoring.enabled", name = "endpoint",
havingValue = "true", matchIfMissing = true)
public CustomEndpoint customEndpoint() {
return new CustomEndpoint();
}
}
MonitoringProperties.java
package com.example.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "monitoring")
public class MonitoringProperties {
private final Enabled enabled = new Enabled();
private String metricsPrefix = "app";
private String healthCheckUrl;
public static class Enabled {
private boolean metrics = true;
private boolean health = true;
private boolean endpoint = true;
// getter/setter
public boolean isMetrics() { return metrics; }
public void setMetrics(boolean metrics) { this.metrics = metrics; }
public boolean isHealth() { return health; }
public void setHealth(boolean health) { this.health = health; }
public boolean isEndpoint() { return endpoint; }
public void setEndpoint(boolean endpoint) { this.endpoint = endpoint; }
}
// getter/setter
public Enabled getEnabled() { return enabled; }
public String getMetricsPrefix() { return metricsPrefix; }
public void setMetricsPrefix(String metricsPrefix) { this.metricsPrefix = metricsPrefix; }
public String getHealthCheckUrl() { return healthCheckUrl; }
public void setHealthCheckUrl(String healthCheckUrl) { this.healthCheckUrl = healthCheckUrl; }
}
3.3 带数据库的 Starter
DatabaseAutoConfiguration.java
package com.example.autoconfigure;
import com.example.repository.CustomRepository;
import com.example.service.DatabaseService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@AutoConfiguration(after = DataSourceAutoConfiguration.class)
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(DatabaseProperties.class)
public class DatabaseAutoConfiguration {
private final DatabaseProperties properties;
public DatabaseAutoConfiguration(DatabaseProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate template = new JdbcTemplate(dataSource);
template.setFetchSize(properties.getFetchSize());
template.setQueryTimeout(properties.getQueryTimeout());
return template;
}
@Bean
@ConditionalOnMissingBean
public CustomRepository customRepository(JdbcTemplate jdbcTemplate) {
return new CustomRepository(jdbcTemplate, properties.getTablePrefix());
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "database.service", name = "enabled",
havingValue = "true", matchIfMissing = true)
public DatabaseService databaseService(CustomRepository repository) {
return new DatabaseService(repository);
}
}
DatabaseProperties.java
package com.example.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "database")
public class DatabaseProperties {
/**
* 表前缀
*/
private String tablePrefix = "t_";
/**
* 查询超时时间(秒)
*/
private int queryTimeout = 30;
/**
* 获取数据的批量大小
*/
private int fetchSize = 1000;
/**
* 批量插入大小
*/
private int batchSize = 1000;
// getter/setter
public String getTablePrefix() { return tablePrefix; }
public void setTablePrefix(String tablePrefix) { this.tablePrefix = tablePrefix; }
public int getQueryTimeout() { return queryTimeout; }
public void setQueryTimeout(int queryTimeout) { this.queryTimeout = queryTimeout; }
public int getFetchSize() { return fetchSize; }
public void setFetchSize(int fetchSize) { this.fetchSize = fetchSize; }
public int getBatchSize() { return batchSize; }
public void setBatchSize(int batchSize) { this.batchSize = batchSize; }
}
3.4 多自动配置 Starter
项目结构
multi-starter/
├── src/main/java/
│ └── com/example/
│ ├── autoconfigure/
│ │ ├── RedisAutoConfiguration.java
│ │ ├── RedisProperties.java
│ │ ├── CacheAutoConfiguration.java
│ │ ├── CacheProperties.java
│ │ └── MultiAutoConfiguration.java
│ └── service/
│ ├── RedisService.java
│ └── CacheService.java
└── src/main/resources/
└── META-INF/
└── spring.factories
MultiAutoConfiguration.java
package com.example.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Import;
@AutoConfiguration
@Import({
RedisAutoConfiguration.class,
CacheAutoConfiguration.class
})
public class MultiAutoConfiguration {
// 组合多个自动配置
}
RedisAutoConfiguration.java
package com.example.autoconfigure;
import com.example.service.RedisService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(RedisService.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
private final RedisProperties properties;
public RedisAutoConfiguration(RedisProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "redis.enabled", havingValue = "true", matchIfMissing = true)
public RedisService redisService() {
return new RedisService(properties.getHost(), properties.getPort());
}
}
CacheAutoConfiguration.java
package com.example.autoconfigure;
import com.example.service.CacheService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration(after = RedisAutoConfiguration.class)
@ConditionalOnClass(CacheService.class)
@EnableConfigurationProperties(CacheProperties.class)
public class CacheAutoConfiguration {
private final CacheProperties properties;
private final RedisService redisService; // 依赖 RedisAutoConfiguration
public CacheAutoConfiguration(CacheProperties properties,
@Autowired(required = false) RedisService redisService) {
this.properties = properties;
this.redisService = redisService;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "cache.enabled", havingValue = "true", matchIfMissing = true)
public CacheService cacheService() {
if (redisService != null) {
return new CacheService(redisService, properties.getTtl());
}
return new CacheService(properties.getTtl());
}
}
3.5 条件注解组合
package com.example.autoconfigure;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
public class ConditionalConfig {
// 类存在条件
@Bean
@ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver")
public Object mysqlDriver() {
return "MySQL Driver available";
}
// Bean 存在条件
@Bean
@ConditionalOnBean(DataSource.class)
public Object dataSourceDependent() {
return "DataSource exists";
}
// Bean 不存在条件
@Bean
@ConditionalOnMissingBean(CacheService.class)
public Object defaultCache() {
return "Default cache";
}
// 属性条件
@Bean
@ConditionalOnProperty(prefix = "app.feature", name = "enabled", havingValue = "true")
public Object featureEnabled() {
return "Feature enabled";
}
// 资源存在条件
@Bean
@ConditionalOnResource(resources = "classpath:feature.properties")
public Object resourceBased() {
return "Resource exists";
}
// Web 应用条件
@Bean
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public Object webOnly() {
return "Web application only";
}
// 非Web应用条件
@Bean
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.NONE)
public Object nonWebOnly() {
return "Non-web application only";
}
// 表达式条件
@Bean
@ConditionalOnExpression("${app.feature.enabled:false} and '${app.mode}' == 'prod'")
public Object expressionBased() {
return "Expression matched";
}
// 组合条件:所有条件都满足
@Bean
@ConditionalOnClass(name = "redis.clients.jedis.Jedis")
@ConditionalOnProperty(prefix = "app.redis", name = "host")
@ConditionalOnMissingBean(RedisClient.class)
public RedisClient redisClient() {
return new RedisClient();
}
}
3.6 配置元数据
spring-configuration-metadata.json
{
"groups": [
{
"name": "my.service",
"type": "com.example.autoconfigure.MyProperties",
"sourceType": "com.example.autoconfigure.MyProperties",
"description": "My service configuration properties."
}
],
"properties": [
{
"name": "my.service.enabled",
"type": "java.lang.Boolean",
"description": "Enable my service.",
"defaultValue": true,
"sourceType": "com.example.autoconfigure.MyProperties"
},
{
"name": "my.service.name",
"type": "java.lang.String",
"description": "Service name.",
"defaultValue": "default-service"
},
{
"name": "my.service.timeout",
"type": "java.lang.Integer",
"description": "Request timeout in milliseconds.",
"defaultValue": 3000,
"deprecation": {
"level": "warning",
"reason": "Use my.service.request-timeout instead.",
"replacement": "my.service.request-timeout"
}
},
{
"name": "my.service.retry-count",
"type": "java.lang.Integer",
"description": "Number of retry attempts.",
"defaultValue": 3
}
],
"hints": [
{
"name": "my.service.log-level",
"values": [
{
"value": "debug",
"description": "Debug level logging."
},
{
"value": "info",
"description": "Info level logging."
},
{
"value": "warn",
"description": "Warning level logging."
},
{
"value": "error",
"description": "Error level logging."
}
]
},
{
"name": "my.service.cache-type",
"values": [
{
"value": "redis",
"description": "Use Redis as cache backend."
},
{
"value": "local",
"description": "Use local memory as cache backend."
},
{
"value": "none",
"description": "Disable caching."
}
]
}
]
}
四、实战应用场景
4.1 日志记录 Starter
// LogAutoConfiguration.java
package com.example.autoconfigure;
import com.example.log.*;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(LogService.class)
@EnableConfigurationProperties(LogProperties.class)
public class LogAutoConfiguration {
private final LogProperties properties;
public LogAutoConfiguration(LogProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
public LogService logService() {
LogService service = new LogService();
service.setLevel(properties.getLevel());
service.setPattern(properties.getPattern());
service.setOutput(properties.getOutput());
return service;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnWebApplication
public LogFilter logFilter(LogService logService) {
return new LogFilter(logService, properties);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "log.aspect", name = "enabled", havingValue = "true")
public LogAspect logAspect(LogService logService) {
return new LogAspect(logService, properties);
}
}
// LogProperties.java
@ConfigurationProperties(prefix = "log")
public class LogProperties {
private String level = "INFO";
private String pattern = "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n";
private String output = "console";
private final Aspect aspect = new Aspect();
public static class Aspect {
private boolean enabled = true;
private String pointcut = "execution(* com.example..*.*(..))";
// getter/setter
}
// getter/setter
}
4.2 API 文档 Starter
// ApiDocAutoConfiguration.java
package com.example.autoconfigure;
import com.example.doc.*;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(ApiDocService.class)
@EnableConfigurationProperties(ApiDocProperties.class)
public class ApiDocAutoConfiguration {
private final ApiDocProperties properties;
public ApiDocAutoConfiguration(ApiDocProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "api-doc", name = "enabled", havingValue = "true", matchIfMissing = true)
public ApiDocService apiDocService() {
return new ApiDocService(
properties.getTitle(),
properties.getDescription(),
properties.getVersion(),
properties.getBasePackage()
);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnWebApplication
public ApiDocController apiDocController(ApiDocService apiDocService) {
return new ApiDocController(apiDocService, properties.getPath());
}
}
// ApiDocProperties.java
@ConfigurationProperties(prefix = "api-doc")
public class ApiDocProperties {
private boolean enabled = true;
private String title = "API Documentation";
private String description = "RESTful API Documentation";
private String version = "1.0.0";
private String basePackage = "";
private String path = "/api-docs";
// getter/setter
}
五、总结与最佳实践
Starter 开发规范
-
命名规范
- 遵循官方命名约定
- 名称简洁明确
-
依赖管理
- 使用 optional 标记可选依赖
- 避免版本冲突
-
自动配置
- 合理使用条件注解
- 提供合理的默认值
- 允许用户覆盖
-
文档完善
- 提供配置元数据
- 编写使用文档
- 添加示例代码
最佳实践清单
-
条件注解使用
- @ConditionalOnClass:类存在时生效
- @ConditionalOnMissingBean:允许覆盖
- @ConditionalOnProperty:按需启用
-
属性设计
- 提供合理的默认值
- 添加验证注解
- 文档化属性
-
自动配置顺序
- 使用 @AutoConfigureBefore/After
- 正确处理依赖关系
-
测试覆盖
- 编写自动配置测试
- 测试各种条件组合
Spring Boot Starter 是简化项目配置和依赖管理的重要方式。通过遵循规范和最佳实践,开发者可以创建高质量的 Starter,提高团队开发效率和代码复用性。