1. SpringBoot核心自动配置原理、SPI机制深度解析

一、Spring Boot 自动配置原理

1.1 核心注解:@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {
    // 核心是 @EnableAutoConfiguration
}
1.2 @EnableAutoConfiguration 机制
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // 导入 AutoConfigurationImportSelector
}

AutoConfigurationImportSelector 工作流程:

public class AutoConfigurationImportSelector implements ... {
    
    // 1. 加载自动配置类
    protected List<String> getCandidateConfigurations(
            AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // 关键:从 META-INF/spring.factories 加载配置
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        return configurations;
    }
    
    // 2. 自动配置类筛选
    protected List<String> getAutoConfigurationEntry(...) {
        // 2.1 获取所有候选配置
        List<String> configurations = getCandidateConfigurations(...);
        
        // 2.2 去重
        configurations = removeDuplicates(configurations);
        
        // 2.3 排除指定配置(@EnableAutoConfiguration.exclude)
        Set<String> exclusions = getExclusions(...);
        configurations.removeAll(exclusions);
        
        // 2.4 应用过滤条件(@Conditional)
        configurations = getConfigurationClassFilter().filter(configurations);
        
        // 2.5 触发自动配置导入事件
        fireAutoConfigurationImportEvents(...);
        
        return new AutoConfigurationEntry(configurations, exclusions);
    }
}
1.3 条件注解(@Conditional)机制
// 核心条件注解类
@ConditionalOnClass        // 类路径存在指定类
@ConditionalOnMissingClass // 类路径不存在指定类
@ConditionalOnBean         // 容器中存在指定Bean
@ConditionalOnMissingBean  // 容器中不存在指定Bean
@ConditionalOnProperty     // 配置属性满足条件
@ConditionalOnResource     // 资源文件存在
@ConditionalOnWebApplication // 是Web应用
@ConditionalOnNotWebApplication // 不是Web应用
@ConditionalOnExpression   // SpEL表达式为true

示例:DataSource 自动配置

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, 
          DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
    
    @Configuration(proxyBeanMethods = false)
    @Conditional(EmbeddedDatabaseCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import(EmbeddedDataSourceConfiguration.class)
    protected static class EmbeddedDatabaseConfiguration {
    }
    
    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ DataSourceConfiguration.Hikari.class,
              DataSourceConfiguration.Tomcat.class,
              DataSourceConfiguration.Dbcp2.class,
              DataSourceConfiguration.Generic.class })
    protected static class PooledDataSourceConfiguration {
    }
}
1.4 自动配置加载流程

二、Spring SPI 机制深度解析

2.1 Java SPI 机制回顾

Java SPI 标准实现:

// 1. 定义接口
public interface DatabaseDriver {
    String connect(String url);
}

// 2. 实现类
public class MySQLDriver implements DatabaseDriver {
    public String connect(String url) {
        return "MySQL连接:" + url;
    }
}

// 3. META-INF/services/com.example.DatabaseDriver 文件内容:
// com.example.MySQLDriver
// com.example.OracleDriver

// 4. 使用 ServiceLoader 加载
ServiceLoader<DatabaseDriver> drivers = 
    ServiceLoader.load(DatabaseDriver.class);
for (DatabaseDriver driver : drivers) {
    System.out.println(driver.connect("localhost:3306"));
}
2.2 Spring 增强的 SPI:SpringFactoriesLoader
public abstract class SpringFactoriesLoader {
    
    // 核心位置
    public static final String FACTORIES_RESOURCE_LOCATION = 
        "META-INF/spring.factories";
    
    // 加载工厂实现
    public static List<String> loadFactoryNames(
            Class<?> factoryType, @Nullable ClassLoader classLoader) {
        
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoader)
                .getOrDefault(factoryTypeName, Collections.emptyList());
    }
    
    // 解析 spring.factories 文件
    private static Map<String, List<String>> loadSpringFactories(
            @Nullable ClassLoader classLoader) {
        
        // 1. 从所有jar包的 META-INF/spring.factories 读取
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        
        // 2. 解析内容
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(
                new UrlResource(url));
            
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                String[] factoryImplementationNames = 
                    StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                
                // 3. 合并所有实现
                result.computeIfAbsent(factoryTypeName, 
                    key -> new ArrayList<>())
                    .addAll(Arrays.asList(factoryImplementationNames));
            }
        }
        
        return result;
    }
}
2.3 Spring Boot 中的 SPI 应用

spring.factories 示例:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration

# Application Context Initializer
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer

# Application Listener
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener
2.4 自定义 Starter 实现
  1. 创建自动配置类:
@Configuration
@ConditionalOnClass(UserService.class)
@EnableConfigurationProperties(UserProperties.class)
public class UserAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public UserService userService(UserProperties properties) {
        return new UserService(properties);
    }
    
    @Bean
    @ConditionalOnProperty(prefix = "user", name = "enable-log", havingValue = "true")
    public UserLogAspect userLogAspect() {
        return new UserLogAspect();
    }
}
  1. 配置属性类:
@ConfigurationProperties(prefix = "user")
public class UserProperties {
    private String name = "default";
    private int age = 18;
    private boolean enableLog = false;
    
    // getters and setters
}
  1. 注册到 spring.factories:
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.user.UserAutoConfiguration

三、自动配置的扩展点

3.1 使用 @Conditional 扩展
// 自定义条件注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnProductionCondition.class)
public @interface ConditionalOnProduction {
}

// 条件判断逻辑
public class OnProductionCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, 
                          AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
        String profile = env.getProperty("spring.profiles.active");
        return "prod".equals(profile);
    }
}
3.2 使用 AutoConfigurationImportFilter
public class CustomAutoConfigurationImportFilter 
        implements AutoConfigurationImportFilter {
    
    private final ConfigurableEnvironment environment;
    
    @Override
    public boolean[] match(String[] autoConfigurationClasses, 
                          AutoConfigurationMetadata metadata) {
        
        boolean[] matches = new boolean[autoConfigurationClasses.length];
        
        for (int i = 0; i < autoConfigurationClasses.length; i++) {
            String className = autoConfigurationClasses[i];
            
            // 自定义过滤逻辑
            if (shouldSkip(className)) {
                matches[i] = false;
            } else {
                matches[i] = true;
            }
        }
        
        return matches;
    }
    
    private boolean shouldSkip(String className) {
        // 根据环境变量、配置等决定是否加载
        return false;
    }
}
3.3 使用 AutoConfigurationImportListener
public class CustomAutoConfigurationImportListener 
        implements AutoConfigurationImportListener {
    
    @Override
    public void onAutoConfigurationImportEvent(
            AutoConfigurationImportEvent event) {
        
        // 获取导入的自动配置类
        List<String> candidateConfigurations = 
            event.getCandidateConfigurations();
        
        // 获取排除的配置类
        Set<String> exclusions = event.getExclusions();
        
        // 记录日志、统计信息等
        log.info("导入自动配置类: {}", candidateConfigurations);
    }
}

四、源码级调试技巧

4.1 调试自动配置过程
// 1. 设置调试断点
// AutoConfigurationImportSelector.getAutoConfigurationEntry()
// ConfigurationClassParser.doProcessConfigurationClass()

// 2. 查看加载的自动配置类
// 启动时添加:--debug 参数
// 或设置:logging.level.org.springframework.boot.autoconfigure=DEBUG

// 3. 查看条件注解评估结果
// ConditionEvaluationReportLoggingListener
4.2 理解自动配置报告
# 调试输出示例:
Positive matches:    # 匹配成功的配置
-----------------
   AopAutoConfiguration matched:
      - @ConditionalOnClass found required classes 
        'org.springframework.context.annotation.EnableAspectJAutoProxy', 
        'org.aspectj.lang.annotation.Aspect' (OnClassCondition)
      - @ConditionalOnProperty (spring.aop.auto=true) matched (OnPropertyCondition)

Negative matches:    # 匹配失败的配置
-----------------
   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 
           'javax.jms.ConnectionFactory' (OnClassCondition)

Exclusions:          # 显式排除的配置
-----------
   None

Unconditional classes: # 无条件配置
--------------------
   org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration

五、最佳实践与注意事项

5.1 自动配置最佳实践
// 1. 使用配置属性类
@ConfigurationProperties(prefix = "my.service")
public class MyServiceProperties {
    private int timeout = 5000;
    private String url;
    
    // 提供合理的默认值
}

// 2. 明确的条件注解
@Configuration
@ConditionalOnClass(SomeFeature.class)
@ConditionalOnProperty(prefix = "my", name = "enabled", havingValue = "true")
@AutoConfigureAfter(DataSourceAutoConfiguration.class)  // 指定顺序
public class MyAutoConfiguration {
}

// 3. 提供 Bean 的候选者
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(DataSource.class)
public MyService myService(DataSource dataSource) {
    return new MyService(dataSource);
}
5.2 常见问题排查
// 1. 自动配置不生效
// 检查:spring.factories 文件位置和格式
// 检查:@Conditional 条件是否满足
// 检查:是否有 exclude 排除

// 2. Bean 冲突问题
// 使用 @ConditionalOnMissingBean
// 使用 @Primary 注解
// 使用 @Qualifier 指定

// 3. 配置加载顺序问题
// 使用 @AutoConfigureBefore/@AutoConfigureAfter
// 使用 @AutoConfigureOrder

六、总结

Spring Boot 自动配置的核心机制:

  1. @EnableAutoConfiguration 通过 AutoConfigurationImportSelector 启用
  2. spring.factories 作为 SPI 扩展点,定义自动配置类
  3. @Conditional 系列注解实现条件化配置
  4. SpringFactoriesLoader 提供增强的 SPI 实现

这种设计实现了:

  • 开箱即用 :默认配置满足大部分场景
  • 按需加载 :条件注解控制配置生效
  • 易于扩展 :SPI 机制支持第三方集成
  • 灵活覆盖 :用户配置优先于自动配置

理解这些原理有助于:

  • 深度定制 Spring Boot 应用
  • 开发高质量的 Starter
  • 解决复杂的配置问题
  • 优化应用启动性能

2. yml/profiles多环境配置、配置优先级、自定义配置绑定

Spring Boot 的多环境配置、优先级和自定义绑定是核心功能。以下是详细说明和示例:

多环境配置 (Profiles)

1.1 配置文件命名约定
application.yml          # 主配置
application-dev.yml      # 开发环境
application-test.yml     # 测试环境
application-prod.yml     # 生产环境
1.2 激活方式

方式1:配置文件指定

# application.yml
spring:
  profiles:
    active: dev

方式2:命令行激活

java -jar app.jar --spring.profiles.active=prod

方式3:系统环境变量

export SPRING_PROFILES_ACTIVE=prod

方式4:JVM参数

java -Dspring.profiles.active=test -jar app.jar
1.3 配置文件示例

主配置 (application.yml)

# 公共配置
app:
  name: MyApplication
  version: 1.0.0

spring:
  profiles:
    active: @activatedProperties@  # Maven/Gradle占位符

logging:
  level:
    root: INFO

开发环境 (application-dev.yml)

# 开发环境配置
server:
  port: 8080
  servlet:
    context-path: /dev-api

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/dev_db
    username: dev_user
    password: dev_pass
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    host: localhost
    port: 6379

app:
  env: development
  debug: true

生产环境 (application-prod.yml)

# 生产环境配置
server:
  port: 80
  servlet:
    context-path: /api

spring:
  datasource:
    url: jdbc:mysql://prod-db:3306/prod_db
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 20
  redis:
    cluster:
      nodes: redis1:6379,redis2:6379,redis3:6379
    password: ${REDIS_PASSWORD}

app:
  env: production
  debug: false

配置优先级(从高到低)

2.1 优先级顺序
1. 命令行参数 (--key=value)
2. SPRING_APPLICATION_JSON 环境变量
3. ServletConfig 初始化参数
4. ServletContext 初始化参数
5. JNDI 属性
6. Java 系统属性 (System.getProperties())
7. 操作系统环境变量
8. RandomValuePropertySource
9. 打包在 jar 外的 Profile-specific 配置文件
10. 打包在 jar 内的 Profile-specific 配置文件
11. 打包在 jar 外的 application.yml/application.properties
12. 打包在 jar 内的 application.yml/application.properties
13. @PropertySource 注解
14. 默认属性 (SpringApplication.setDefaultProperties)
2.2 覆盖示例
# 优先级演示
# application.yml
server:
  port: 8080

# 通过命令行覆盖
# java -jar app.jar --server.port=9090

# 通过环境变量覆盖
# export SERVER_PORT=9090

自定义配置绑定

3.1 @ConfigurationProperties 方式

配置类

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

@Component
@ConfigurationProperties(prefix = "app.config")
@Data
public class AppConfig {
    
    private String name;
    private String version;
    private Database database;
    private Security security;
    private List<String> whiteList;
    
    @Data
    public static class Database {
        private String url;
        private String username;
        private String password;
        private Integer maxConnections;
    }
    
    @Data
    public static class Security {
        private String secretKey;
        private Long tokenExpire;
        private Boolean enableTwoFactor;
    }
}

配置文件

app:
  config:
    name: "MyApp"
    version: "2.0.0"
    database:
      url: "jdbc:mysql://localhost:3306/mydb"
      username: "admin"
      password: "secret"
      max-connections: 50
    security:
      secret-key: "my-secret-key-123"
      token-expire: 3600
      enable-two-factor: true
    white-list:
      - "192.168.1.1"
      - "192.168.1.2"
      - "192.168.1.3"
3.2 @Value 方式
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class CustomConfig {
    
    @Value("${app.name:defaultApp}")  // 默认值
    private String appName;
    
    @Value("${server.port}")
    private Integer serverPort;
    
    @Value("${app.features.enabled}")
    private Boolean featuresEnabled;
    
    @Value("#{'${app.ips}'.split(',')}")
    private List<String> ipList;
    
    @Value("#{${app.map}}")
    private Map<String, String> configMap;
}
3.3 复杂类型绑定
@Component
@ConfigurationProperties(prefix = "app")
@Data
@Validated  // 支持JSR-303验证
public class ApplicationProperties {
    
    @NotNull
    @Size(min = 1, max = 50)
    private String name;
    
    @Email
    private String adminEmail;
    
    @Min(1)
    @Max(65535)
    private Integer maxUsers;
    
    @Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
    private String contactEmail;
    
    @Valid  // 嵌套对象验证
    private DatabaseConfig database;
    
    @Data
    public static class DatabaseConfig {
        @NotEmpty
        private String url;
        
        @Min(1)
        @Max(100)
        private Integer poolSize;
    }
}
3.4 属性转换器
@Component
@ConfigurationPropertiesBinding
public class StringToDurationConverter implements Converter<String, Duration> {
    
    @Override
    public Duration convert(String source) {
        return Duration.parse(source);
    }
}

// 使用
@Component
@ConfigurationProperties(prefix = "app.time")
@Data
public class TimeConfig {
    private Duration timeout;  // 自动转换 "PT30S" -> Duration
}

最佳实践示例

4.1 完整的多环境配置示例

目录结构

src/main/resources/
├── application.yml
├── application-dev.yml
├── application-test.yml
├── application-prod.yml
└── config/
    ├── datasource.yml
    └── redis.yml

主配置 (application.yml)

# 公共配置
spring:
  application:
    name: demo-app
  profiles:
    active: @spring.profiles.active@
  config:
    import: 
      - classpath:config/datasource.yml
      - classpath:config/redis.yml

# 日志配置
logging:
  config: classpath:logback-${spring.profiles.active}.xml
  file:
    name: logs/${spring.application.name}.log
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

# 自定义配置
app:
  info:
    name: ${spring.application.name}
    version: @project.version@
    description: "Spring Boot Application"

环境特定配置 (application-prod.yml)

# 生产环境
server:
  port: 8080
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,application/javascript,application/json
    min-response-size: 1024

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
      base-path: /manage
  endpoint:
    health:
      show-details: never

app:
  security:
    cors:
      allowed-origins: https://prod.example.com
      allowed-methods: GET,POST,PUT,DELETE
    jwt:
      secret: ${JWT_SECRET:default-secret-key}
      expiration: 86400
4.2 配置验证类
@Component
@ConfigurationProperties(prefix = "app")
@Data
@Validated
public class AppProperties {
    
    @NotNull
    private String name;
    
    @NotNull
    @Pattern(regexp = "^\\d+\\.\\d+\\.\\d+$")
    private String version;
    
    @Valid
    private ApiConfig api;
    
    @Valid
    private CacheConfig cache;
    
    @Data
    public static class ApiConfig {
        @NotBlank
        private String baseUrl;
        
        @Min(1000)
        @Max(60000)
        private Integer timeout;
        
        @NotNull
        private Boolean retryEnabled;
    }
    
    @Data
    public static class CacheConfig {
        @NotBlank
        private String type;
        
        @Min(1)
        private Integer ttl;
        
        @NotNull
        private Boolean clusterMode;
    }
}
4.3 使用配置类
@Service
public class UserService {
    
    private final AppProperties appProperties;
    
    public UserService(AppProperties appProperties) {
        this.appProperties = appProperties;
    }
    
    public void printConfig() {
        System.out.println("App Name: " + appProperties.getName());
        System.out.println("API Timeout: " + appProperties.getApi().getTimeout());
        System.out.println("Cache TTL: " + appProperties.getCache().getTtl());
    }
}

实用技巧

5.1 配置占位符
app:
  base-url: https://${app.domain:localhost}:${server.port}
  domain: example.com
  endpoints:
    user: ${app.base-url}/api/users
    product: ${app.base-url}/api/products
5.2 列表和Map配置
app:
  servers:
    - name: server1
      host: 192.168.1.1
      port: 8080
    - name: server2
      host: 192.168.1.2
      port: 8080
  
  settings:
    cache.enabled: true
    cache.ttl: 3600
    logging.level: DEBUG
    retry.max-attempts: 3
5.3 条件配置
@Configuration
@ConditionalOnProperty(name = "app.feature.x.enabled", havingValue = "true")
public class FeatureXConfig {
    
    @Bean
    @ConditionalOnMissingBean
    public FeatureXService featureXService() {
        return new FeatureXService();
    }
}

这些示例涵盖了Spring Boot配置的主要方面。根据实际需求选择合适的配置方式,并遵循优先级规则进行配置管理。

3. 全局异常处理、统一返回结果、拦截器、过滤器实战

一、项目结构

src/main/java/com/example/demo/
├── config/
│   ├── WebConfig.java          # Web配置(拦截器、过滤器配置)
│   └── CorsConfig.java         # 跨域配置
├── common/
│   ├── annotation/
│   │   └── NoResponseWrap.java # 不包装响应注解
│   ├── constant/
│   │   └── ResponseCode.java   # 响应状态码
│   ├── entity/
│   │   ├── BaseResponse.java   # 统一响应实体
│   │   └── PageResult.java     # 分页响应实体
│   ├── exception/
│   │   ├── BusinessException.java  # 业务异常
│   │   └── GlobalExceptionHandler.java # 全局异常处理器
│   └── interceptor/
│       ├── AuthInterceptor.java    # 认证拦截器
│       ├── LogInterceptor.java     # 日志拦截器
│       └── RateLimitInterceptor.java # 限流拦截器
├── filter/
│   ├── RequestLogFilter.java       # 请求日志过滤器
│   ├── XssFilter.java              # XSS过滤过滤器
│   └── FilterConfig.java           # 过滤器配置
└── controller/
    └── DemoController.java         # 示例控制器

二、核心代码实现

统一返回结果实体

ResponseCode.java - 响应状态码枚举

package com.example.demo.common.constant;

import lombok.Getter;

@Getter
public enum ResponseCode {
    SUCCESS(200, "操作成功"),
    BAD_REQUEST(400, "参数错误"),
    UNAUTHORIZED(401, "未授权"),
    FORBIDDEN(403, "禁止访问"),
    NOT_FOUND(404, "资源不存在"),
    METHOD_NOT_ALLOWED(405, "请求方法不允许"),
    INTERNAL_SERVER_ERROR(500, "服务器内部错误"),
    SERVICE_UNAVAILABLE(503, "服务不可用"),
    
    // 业务错误码
    USER_NOT_EXIST(1001, "用户不存在"),
    USER_PASSWORD_ERROR(1002, "密码错误"),
    TOKEN_INVALID(1003, "Token无效"),
    TOKEN_EXPIRED(1004, "Token已过期");

    private final int code;
    private final String message;

    ResponseCode(int code, String message) {
        this.code = code;
        this.message = message;
    }
}

BaseResponse.java - 统一响应实体

package com.example.demo.common.entity;

import com.example.demo.common.constant.ResponseCode;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class BaseResponse<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp;

    public BaseResponse(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
        this.timestamp = System.currentTimeMillis();
    }

    // 成功响应
    public static <T> BaseResponse<T> success() {
        return success(null);
    }

    public static <T> BaseResponse<T> success(T data) {
        return new BaseResponse<>(
            ResponseCode.SUCCESS.getCode(),
            ResponseCode.SUCCESS.getMessage(),
            data
        );
    }

    public static <T> BaseResponse<T> success(String message, T data) {
        return new BaseResponse<>(
            ResponseCode.SUCCESS.getCode(),
            message,
            data
        );
    }

    // 失败响应
    public static <T> BaseResponse<T> error() {
        return error(ResponseCode.INTERNAL_SERVER_ERROR);
    }

    public static <T> BaseResponse<T> error(ResponseCode responseCode) {
        return error(responseCode.getCode(), responseCode.getMessage());
    }

    public static <T> BaseResponse<T> error(int code, String message) {
        return new BaseResponse<>(code, message, null);
    }

    public static <T> BaseResponse<T> error(String message) {
        return error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(), message);
    }
}

PageResult.java - 分页响应实体

package com.example.demo.common.entity;

import lombok.Data;
import java.util.List;

@Data
public class PageResult<T> {
    private long total;
    private List<T> list;
    private int pageNum;
    private int pageSize;
    private int pages;

    public PageResult(List<T> list, long total, int pageNum, int pageSize) {
        this.list = list;
        this.total = total;
        this.pageNum = pageNum;
        this.pageSize = pageSize;
        this.pages = (int) Math.ceil((double) total / pageSize);
    }
}
自定义异常

BusinessException.java - 业务异常

package com.example.demo.common.exception;

import com.example.demo.common.constant.ResponseCode;
import lombok.Getter;

@Getter
public class BusinessException extends RuntimeException {
    private final int code;
    
    public BusinessException(String message) {
        super(message);
        this.code = ResponseCode.INTERNAL_SERVER_ERROR.getCode();
    }
    
    public BusinessException(ResponseCode responseCode) {
        super(responseCode.getMessage());
        this.code = responseCode.getCode();
    }
    
    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }
    
    public BusinessException(ResponseCode responseCode, String message) {
        super(message);
        this.code = responseCode.getCode();
    }
}
全局异常处理

GlobalExceptionHandler.java - 全局异常处理器

package com.example.demo.common.exception;

import com.example.demo.common.constant.ResponseCode;
import com.example.demo.common.entity.BaseResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public BaseResponse<Object> handleBusinessException(BusinessException e) {
        log.warn("业务异常: {}", e.getMessage());
        return BaseResponse.error(e.getCode(), e.getMessage());
    }

    /**
     * 处理参数校验异常(@Validated @RequestBody)
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseResponse<Object> handleMethodArgumentNotValidException(
            MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(", "));
        log.warn("参数校验异常: {}", message);
        return BaseResponse.error(ResponseCode.BAD_REQUEST.getCode(), message);
    }

    /**
     * 处理参数校验异常(@Validated 方法参数)
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public BaseResponse<Object> handleConstraintViolationException(
            ConstraintViolationException e) {
        String message = e.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(", "));
        log.warn("参数校验异常: {}", message);
        return BaseResponse.error(ResponseCode.BAD_REQUEST.getCode(), message);
    }

    /**
     * 处理参数绑定异常
     */
    @ExceptionHandler(BindException.class)
    public BaseResponse<Object> handleBindException(BindException e) {
        String message = e.getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(", "));
        log.warn("参数绑定异常: {}", message);
        return BaseResponse.error(ResponseCode.BAD_REQUEST.getCode(), message);
    }

    /**
     * 处理404异常
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public BaseResponse<Object> handleNoHandlerFoundException(
            NoHandlerFoundException e, HttpServletRequest request) {
        log.warn("请求路径不存在: {} {}", request.getMethod(), request.getRequestURI());
        return BaseResponse.error(ResponseCode.NOT_FOUND);
    }

    /**
     * 处理其他所有异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public BaseResponse<Object> handleException(Exception e, HttpServletRequest request) {
        log.error("系统异常: {} {}", request.getMethod(), request.getRequestURI(), e);
        // 生产环境隐藏详细错误信息
        String message = "系统繁忙,请稍后再试";
        // 开发环境显示详细错误
        if (isDevEnvironment()) {
            message = e.getMessage();
        }
        return BaseResponse.error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(), message);
    }

    private boolean isDevEnvironment() {
        // 这里可以根据实际需求判断环境
        return true; // 示例
    }
}
响应包装切面

ResponseAdvice.java - 响应包装切面

package com.example.demo.common.advice;

import com.example.demo.common.annotation.NoResponseWrap;
import com.example.demo.common.entity.BaseResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 统一响应包装切面
 */
@Slf4j
@RestControllerAdvice
@RequiredArgsConstructor
public class ResponseAdvice implements ResponseBodyAdvice<Object> {

    private final ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, 
                           Class<? extends HttpMessageConverter<?>> converterType) {
        // 检查类或方法上是否有@NoResponseWrap注解
        boolean hasClassAnnotation = returnType.getContainingClass()
                .isAnnotationPresent(NoResponseWrap.class);
        boolean hasMethodAnnotation = returnType.hasMethodAnnotation(NoResponseWrap.class);
        
        // 如果已经返回BaseResponse,不再包装
        if (returnType.getParameterType().equals(BaseResponse.class)) {
            return false;
        }
        
        // 如果有@NoResponseWrap注解,不包装
        return !(hasClassAnnotation || hasMethodAnnotation);
    }

    @Override
    public Object beforeBodyWrite(Object body, 
                                 MethodParameter returnType,
                                 MediaType selectedContentType,
                                 Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                 ServerHttpRequest request,
                                 ServerHttpResponse response) {
        
        // 处理String类型返回值
        if (body instanceof String) {
            try {
                response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                return objectMapper.writeValueAsString(BaseResponse.success(body));
            } catch (JsonProcessingException e) {
                log.error("响应包装异常", e);
                return BaseResponse.error("响应序列化失败");
            }
        }
        
        // 处理void返回类型
        if (body == null && returnType.getParameterType().equals(void.class)) {
            return BaseResponse.success();
        }
        
        return BaseResponse.success(body);
    }
}

NoResponseWrap.java - 不包装响应注解

package com.example.demo.common.annotation;

import java.lang.annotation.*;

/**
 * 标记不需要统一包装响应的接口
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoResponseWrap {
}
拦截器实现

AuthInterceptor.java - 认证拦截器

package com.example.demo.common.interceptor;

import com.example.demo.common.constant.ResponseCode;
import com.example.demo.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
public class AuthInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        String token = request.getHeader("Authorization");
        String requestURI = request.getRequestURI();
        
        log.info("认证拦截器: {} {}", request.getMethod(), requestURI);
        
        // 放行登录接口
        if (requestURI.contains("/api/auth/login")) {
            return true;
        }
        
        // 验证token
        if (token == null || token.isEmpty()) {
            throw new BusinessException(ResponseCode.UNAUTHORIZED);
        }
        
        // 这里可以添加token验证逻辑
        if (!isValidToken(token)) {
            throw new BusinessException(ResponseCode.TOKEN_INVALID);
        }
        
        // 设置用户信息到request
        request.setAttribute("userId", extractUserIdFromToken(token));
        
        return true;
    }
    
    private boolean isValidToken(String token) {
        // 实际项目中这里应该验证token的有效性
        return token.startsWith("Bearer ");
    }
    
    private Long extractUserIdFromToken(String token) {
        // 实际项目中这里应该从token中解析用户信息
        return 1L;
    }
}

LogInterceptor.java - 日志拦截器

package com.example.demo.common.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
public class LogInterceptor implements HandlerInterceptor {

    private static final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) {
        startTimeThreadLocal.set(System.currentTimeMillis());
        log.info("请求开始: {} {}, 参数: {}", 
                request.getMethod(), 
                request.getRequestURI(),
                request.getQueryString());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, 
                          HttpServletResponse response, 
                          Object handler,
                          ModelAndView modelAndView) {
        // 可以在这里处理响应数据
    }

    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response, 
                               Object handler, 
                               Exception ex) {
        Long startTime = startTimeThreadLocal.get();
        if (startTime != null) {
            long duration = System.currentTimeMillis() - startTime;
            log.info("请求结束: {} {}, 耗时: {}ms, 状态码: {}", 
                    request.getMethod(), 
                    request.getRequestURI(),
                    duration,
                    response.getStatus());
            startTimeThreadLocal.remove();
        }
        
        if (ex != null) {
            log.error("请求异常: {}", ex.getMessage(), ex);
        }
    }
}

RateLimitInterceptor.java - 限流拦截器

package com.example.demo.common.interceptor;

import com.example.demo.common.constant.ResponseCode;
import com.example.demo.common.exception.BusinessException;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class RateLimitInterceptor implements HandlerInterceptor {

    // 使用Guava的RateLimiter进行限流
    private final ConcurrentHashMap<String, RateLimiter> limiters = 
        new ConcurrentHashMap<>();
    
    // 默认限流:每秒10个请求
    private static final double DEFAULT_RATE = 10.0;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) {
        String ip = getClientIp(request);
        String key = "rate_limit:" + ip;
        
        RateLimiter limiter = limiters.computeIfAbsent(
            key, k -> RateLimiter.create(DEFAULT_RATE)
        );
        
        if (!limiter.tryAcquire()) {
            log.warn("IP {} 请求过于频繁", ip);
            throw new BusinessException(ResponseCode.SERVICE_UNAVAILABLE, "请求过于频繁,请稍后再试");
        }
        
        return true;
    }
    
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

4. SpringBoot性能优化、启动提速、冗余依赖剔除

SpringBoot 应用性能优化和启动提速是一个系统工程,涉及多个层面。以下从 启动优化 、运行时优化 、依赖治理 和 部署优化 四个维度提供具体方案。


一、启动阶段优化

1. 延迟初始化(Lazy Initialization)
# application.yml
spring:
  main:
    lazy-initialization: true  # 所有Bean延迟初始化
  • 优点 :减少启动时创建的Bean数量,加快启动速度
  • 缺点 :首次请求响应时间变长,可能掩盖启动时的配置问题
  • 折中方案 :仅对特定Bean使用 @Lazy
2. 组件扫描优化
@SpringBootApplication(
    scanBasePackages = "com.your.package",  // 精确指定扫描范围
    exclude = {
        DataSourceAutoConfiguration.class,  // 排除不需要的自动配置
        CacheAutoConfiguration.class
    }
)
3. 类路径优化
# 使用JAR索引文件
java -Djarmode=layertools -jar app.jar extract
java -Dspring.boot.classpath.index=./layers/application/classpath.idx -jar app.jar
4. 编译时优化(Spring Native / GraalVM)
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.experimental</groupId>
    <artifactId>spring-native</artifactId>
    <version>0.12.1</version>
</dependency>
# 编译为原生镜像
mvn spring-boot:build-image
5. Spring Context 索引(@Indexed)
// 在常用注解上添加@Indexed
@Indexed
@Component
public @interface MyComponent {
}

生成 ​​META-INF/spring.components​​ 文件加速扫描。


二、运行时性能优化

1. JVM参数调优
# 生产环境推荐配置
java -server \
     -Xms2g -Xmx2g \           # 堆大小固定,避免动态调整
     -XX:MetaspaceSize=256m \
     -XX:MaxMetaspaceSize=256m \
     -XX:+UseG1GC \            # G1垃圾回收器
     -XX:MaxGCPauseMillis=200 \
     -XX:+UseStringDeduplication \
     -XX:+HeapDumpOnOutOfMemoryError \
     -jar app.jar
2. 连接池优化(HikariCP)
spring:
  datasource:
    hikari:
      maximum-pool-size: 20           # 根据CPU核心数调整
      minimum-idle: 5
      connection-timeout: 3000
      idle-timeout: 600000
      max-lifetime: 1800000
      connection-test-query: SELECT 1
3. 缓存优化
// 使用Caffeine作为本地缓存
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();
        manager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES)
            .maximumSize(1000));
        return manager;
    }
}
4. 异步处理
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

三、依赖治理与瘦身

1. 依赖分析工具
# 查看依赖树
mvn dependency:tree -Dverbose > tree.txt

# 使用Maven Enforcer插件禁止传递依赖
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-enforcer-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
        <execution>
            <id>enforce-banned-dependencies</id>
            <goals><goal>enforce</goal></goals>
            <configuration>
                <rules>
                    <bannedDependencies>
                        <excludes>
                            <exclude>commons-logging:commons-logging</exclude>
                            <exclude>log4j:log4j</exclude>
                        </excludes>
                    </bannedDependencies>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>
2. SpringBoot依赖分析
# 使用SpringBoot提供的分析工具
java -jar app.jar --debug
# 或使用Actuator端点
curl http://localhost:8080/actuator/conditions
3. 排除不必要的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
4. 使用JAR瘦身插件
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <layers>
            <enabled>true</enabled>
        </layers>
        <excludes>
            <exclude>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </exclude>
        </excludes>
    </configuration>
</plugin>
5. 模块化打包(Layer Tools)
# 分层打包Docker镜像
FROM adoptopenjdk:11-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM adoptopenjdk:11-jre-hotspot
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

四、监控与诊断

1. 启动耗时分析
@SpringBootApplication
public class Application implements ApplicationRunner {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setApplicationStartup(new BufferingApplicationStartup(2048));
        app.run(args);
    }
    
    @Override
    public void run(ApplicationArguments args) {
        // 查看启动指标
        // curl http://localhost:8080/actuator/startup
    }
}
2. 使用Arthas诊断
# 在线诊断工具
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 常用命令
dashboard          # 仪表板
trace *Controller* # 追踪方法调用
jad com.example.Class # 反编译
3. JMX监控
spring:
  jmx:
    enabled: true
management:
  endpoints:
    jmx:
      exposure:
        include: "*"

五、最佳实践清单

✅ 必须做的:
  1. 启用SpringBoot Actuator 监控关键指标
  2. 使用最新稳定版SpringBoot (每个版本都有性能改进)
  3. 合理设置JVM参数 ,特别是堆大小和GC算法
  4. 生产环境关闭DevTools
  5. 使用@Profile区分环境配置
⚠️ 建议做的:
  1. 定期运行依赖检查 :mvn versions:display-dependency-updates
  2. 使用Docker层缓存 优化镜像构建
  3. 启用HTTP/2 (需要SSL)
  4. 配置合理的连接池参数
  5. 使用编译时注解处理器 (如MapStruct、Lombok)
🔧 高级优化:
  1. 考虑使用Quarkus/Micronaut 替代SpringBoot(对启动时间要求极高时)
  2. 实施特性开关 减少不必要的功能加载
  3. 数据库连接预热 (启动后执行简单查询)
  4. 使用RSocket替代HTTP (内部服务通信)
  5. 实施渐进式交付 (蓝绿部署)

六、快速检查清单

# 1. 检查JAR大小
ls -lh target/*.jar

# 2. 检查启动时间
time java -jar app.jar

# 3. 检查依赖数量
mvn dependency:list | wc -l

# 4. 检查自动配置
java -jar app.jar --debug | grep -A5 -B5 "Positive matches"

# 5. 生成原生镜像分析报告
native-image --enable-monitoring=heapdump app

通过以上多维度优化,通常可以将SpringBoot应用启动时间减少30%-70%,内存占用降低20%-50%。建议根据实际监控数据,采取有针对性的优化措施。

5. 快速搭建可复用的SpringBoot项目脚手架

一、项目结构设计

标准Maven多模块结构

springboot-scaffold/
├── scaffold-parent              # 父POM
├── scaffold-common              # 通用模块
├── scaffold-core                # 核心业务模块
├── scaffold-api                 # API接口模块
├── scaffold-dao                 # 数据访问层
└── scaffold-web                 # Web层

二、基础依赖配置

父POM配置

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version>
    </parent>
    
    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        
        <!-- 常用依赖版本 -->
        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
        <knife4j.version>4.3.0</knife4j.version>
        <mapstruct.version>1.5.5.Final</mapstruct.version>
        <hutool.version>5.8.21</hutool.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <!-- Web -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            
            <!-- Validation -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-validation</artifactId>
            </dependency>
            
            <!-- MyBatis Plus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            
            <!-- Knife4j API文档 -->
            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
                <version>${knife4j.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

三、核心配置类

全局异常处理

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException e) {
        log.error("业务异常: {}", e.getMessage(), e);
        return Result.error(e.getCode(), e.getMessage());
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValidException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining("; "));
        return Result.error(ErrorCode.PARAM_ERROR.getCode(), message);
    }
    
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        log.error("系统异常: ", e);
        return Result.error(ErrorCode.SYSTEM_ERROR);
    }
}

统一响应封装

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp;
    
    public static <T> Result<T> success() {
        return success(null);
    }
    
    public static <T> Result<T> success(T data) {
        return Result.<T>builder()
                .code(ErrorCode.SUCCESS.getCode())
                .message(ErrorCode.SUCCESS.getMessage())
                .data(data)
                .timestamp(System.currentTimeMillis())
                .build();
    }
    
    public static <T> Result<T> error(ErrorCode errorCode) {
        return Result.<T>builder()
                .code(errorCode.getCode())
                .message(errorCode.getMessage())
                .timestamp(System.currentTimeMillis())
                .build();
    }
    
    public static <T> Result<T> error(Integer code, String message) {
        return Result.<T>builder()
                .code(code)
                .message(message)
                .timestamp(System.currentTimeMillis())
                .build();
    }
}

错误码枚举

@Getter
@AllArgsConstructor
public enum ErrorCode {
    SUCCESS(200, "成功"),
    PARAM_ERROR(400, "参数错误"),
    UNAUTHORIZED(401, "未授权"),
    FORBIDDEN(403, "禁止访问"),
    NOT_FOUND(404, "资源不存在"),
    SYSTEM_ERROR(500, "系统内部错误"),
    BUSINESS_ERROR(1000, "业务异常");
    
    private final Integer code;
    private final String message;
}

四、数据层配置

MyBatis Plus配置

@Configuration
@MapperScan("com.yourpackage.mapper")
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
    
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseGeneratedKeys(false);
    }
}

基础实体类

@Data
public class BaseEntity {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    @TableLogic
    private Integer deleted;
}

通用Mapper

public interface BaseMapper<T extends BaseEntity> extends com.baomidou.mybatisplus.core.mapper.BaseMapper<T> {
    
    default Page<T> selectPage(PageParam pageParam) {
        return selectPage(new Page<>(pageParam.getPageNum(), pageParam.getPageSize()), null);
    }
    
    default Page<T> selectPage(PageParam pageParam, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
        return selectPage(new Page<>(pageParam.getPageNum(), pageParam.getPageSize()), queryWrapper);
    }
}

五、工具类封装

分页参数

@Data
public class PageParam {
    @Min(value = 1, message = "页码不能小于1")
    private Integer pageNum = 1;
    
    @Min(value = 1, message = "每页条数不能小于1")
    @Max(value = 100, message = "每页条数不能大于100")
    private Integer pageSize = 10;
    
    private String orderBy;
    private Boolean asc = true;
}

分页结果

@Data
@Builder
public class PageResult<T> {
    private List<T> records;
    private Long total;
    private Integer pageNum;
    private Integer pageSize;
    private Integer pages;
    
    public static <T> PageResult<T> of(Page<T> page) {
        return PageResult.<T>builder()
                .records(page.getRecords())
                .total(page.getTotal())
                .pageNum((int) page.getCurrent())
                .pageSize((int) page.getSize())
                .pages((int) page.getPages())
                .build();
    }
}

六、API文档配置

Swagger/Knife4j配置

@Configuration
@EnableOpenApi
public class SwaggerConfig {
    
    @Bean
    public OpenAPI springShopOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("API文档")
                        .description("SpringBoot脚手架API文档")
                        .version("v1.0")
                        .contact(new Contact()
                                .name("开发者")
                                .email("dev@example.com")))
                .externalDocs(new ExternalDocumentation()
                        .description("项目文档")
                        .url("https://github.com/your-project"));
    }
}

七、应用配置

application.yml

spring:
  application:
    name: scaffold-demo
  datasource:
    url: jdbc:mysql://localhost:3306/scaffold?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
  
  # Redis配置
  redis:
    host: localhost
    port: 6379
    database: 0
    timeout: 3000ms
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 5
  
  # 文件上传
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB

# MyBatis Plus配置
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  type-aliases-package: com.yourpackage.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

# 日志配置
logging:
  level:
    com.yourpackage: debug
    org.springframework.web: info
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

# 自定义配置
app:
  jwt:
    secret: your-jwt-secret-key
    expire: 7200
  upload:
    path: /data/upload/
    max-size: 10MB

八、快速启动脚本

一键生成脚本

#!/bin/bash
# scaffold-generator.sh

echo "开始生成SpringBoot脚手架项目..."

# 创建项目目录
mkdir -p springboot-scaffold/{common,core,api,dao,web}

# 复制配置文件
cp -r template/* springboot-scaffold/

# 初始化Git仓库
cd springboot-scaffold
git init
git add .
git commit -m "Initial commit: SpringBoot脚手架项目"

echo "项目生成完成!"
echo "请修改以下文件:"
echo "1. application.yml 中的数据库配置"
echo "2. pom.xml 中的项目信息"
echo "3. 包名 com.yourpackage"

九、最佳实践建议

开发规范

  • 使用Lombok减少样板代码
  • 使用MapStruct进行对象映射
  • 使用Validation进行参数校验
  • 统一日志格式和级别
  • 使用枚举定义状态码和常量

安全建议

  • 添加Spring Security依赖
  • 配置JWT token验证
  • 接口防刷和限流
  • SQL注入防护
  • XSS防护

性能优化

  • 添加Redis缓存
  • 数据库连接池配置
  • 接口响应时间监控
  • 慢SQL日志记录
  • 分页查询优化

监控配置

<!-- 添加监控依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

十、快速使用

  1. 克隆模板仓库
git clone https://github.com/your-template-repo.git
  1. 修改配置
  • 更新pom.xml中的项目信息
  • 配置application.yml中的数据库连接
  • 修改包名为实际项目包名
  1. 运行项目
mvn clean install
mvn spring-boot:run

这个脚手架包含了企业级开发所需的基础组件,您可以根据实际需求进行裁剪和扩展。建议根据具体业务场景添加相应的模块和功能。

更多推荐