22-Spring Boot Starters 开发详解

7 阅读4分钟

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 自动配置类设计

自动配置类应该遵循以下原则:

  1. 条件化加载:使用 @Conditional 注解
  2. 可覆盖:使用 @ConditionalOnMissingBean
  3. 属性绑定:使用 @ConfigurationProperties
  4. 文档完整:添加 Javadoc 注释

2.5 Starter 分类

  1. 应用 Starter:面向特定应用场景

    • spring-boot-starter-web
    • spring-boot-starter-batch
  2. 技术 Starter:面向特定技术

    • spring-boot-starter-data-redis
    • spring-boot-starter-amqp
  3. 生产 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 开发规范

  1. 命名规范

    • 遵循官方命名约定
    • 名称简洁明确
  2. 依赖管理

    • 使用 optional 标记可选依赖
    • 避免版本冲突
  3. 自动配置

    • 合理使用条件注解
    • 提供合理的默认值
    • 允许用户覆盖
  4. 文档完善

    • 提供配置元数据
    • 编写使用文档
    • 添加示例代码

最佳实践清单

  1. 条件注解使用

    • @ConditionalOnClass:类存在时生效
    • @ConditionalOnMissingBean:允许覆盖
    • @ConditionalOnProperty:按需启用
  2. 属性设计

    • 提供合理的默认值
    • 添加验证注解
    • 文档化属性
  3. 自动配置顺序

    • 使用 @AutoConfigureBefore/After
    • 正确处理依赖关系
  4. 测试覆盖

    • 编写自动配置测试
    • 测试各种条件组合

Spring Boot Starter 是简化项目配置和依赖管理的重要方式。通过遵循规范和最佳实践,开发者可以创建高质量的 Starter,提高团队开发效率和代码复用性。