Spring 全家桶 列表全部文章
Spring 全家桶

Spring Boot 实战笔记:自动装配原理、常用扩展点与生产配置清单

讲清 Spring Boot 的自动装配与启动流程,整理日常最常写的扩展点(拦截器、全局异常、参数校验、异步、定时任务、优雅停机、Actuator),附一份生产 application.yml 配置清单和启动慢、配置不生效的排查方法。

更多

Spring Boot 的价值不在「省了 XML」,而在约定大于配置 + 可插拔的 starter 体系。理解自动装配,才知道为什么加个依赖就多了一堆 Bean,以及配置为什么不生效。

一、自动装配是怎么发生的

@SpringBootApplication @EnableAutoConfiguration 导入 AutoConfigurationImportSelector 扫描 META-INF/spring/ *.imports(Boot 3) @Conditional 条件判断 通过才创建 Bean 条件注解:@ConditionalOnClass / OnMissingBean / OnProperty @ConditionalOnMissingBean = 用户自定义优先 调试技巧:启动时加 --debug,控制台输出 CONDITION EVALUATION DELTA,能直接看到 哪些自动配置生效了(Positive matches)、哪些因为什么条件没生效(Negative matches)。

关键点:@ConditionalOnMissingBean 意味着你自己定义的 Bean 会覆盖自动配置。想改 Redis 序列化、想换 ObjectMapper,直接自己定义一个 @Bean 就行,不用排除自动配置类。

二、启动流程(知道节点,才知道在哪插手)

SpringApplication.run()
  → 推断应用类型(Servlet / Reactive)
  → 加载 ApplicationContextInitializer、ApplicationListener
  → 准备 Environment(读 application.yml、命令行参数、环境变量)
  → 创建 ApplicationContext
  → refreshContext(扫描 Bean、自动装配、启动内嵌 Tomcat)
  → 调用 ApplicationRunner / CommandLineRunner
  → 发布 ApplicationReadyEvent

常用的介入点:

想做什么 用什么
启动后执行一次初始化 ApplicationRunner / CommandLineRunner(@Order 排序)
环境准备阶段改配置 EnvironmentPostProcessor
监听启动完成事件 @EventListener(ApplicationReadyEvent.class)
启动失败兜底 FailureAnalyzer

三、必写的几个扩展点

1. 全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BizException.class)
    public Result<?> biz(BizException e) {
        log.warn("biz error: {}", e.getMessage());
        return Result.fail(e.getCode(), e.getMessage());
    }
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> valid(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
                .map(f -> f.getField() + ":" + f.getDefaultMessage())
                .collect(Collectors.joining(", "));
        return Result.fail(400, msg);
    }
    @ExceptionHandler(Exception.class)
    public Result<?> ex(Exception e) {
        log.error("unexpected", e);      // ★ 必须打完整栈,否则没法排查
        return Result.fail(500, "服务繁忙,请稍后重试");
    }
}

对外不要暴露堆栈和 SQL 异常,对内要打完整日志。这两件事不冲突。

2. 参数校验

@Data
public class CreateOrderReq {
    @NotBlank(message = "订单号不能为空")
    private String orderNo;
    @NotNull @Min(value = 1, message = "数量至少为 1")
    private Integer qty;
}
@PostMapping("/order")
public Result<Void> create(@Valid @RequestBody CreateOrderReq req) { ... }

3. 拦截器 vs 过滤器

过滤器 Filter 拦截器 Interceptor
归属 Servlet 规范 Spring MVC
能拿到 原始 request/response Handler 方法、参数
典型用途 编码、跨域、XSS 过滤 登录态、权限、日志 TraceId

4. 异步与定时任务

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean("bizExecutor")
    public Executor bizExecutor() {
        ThreadPoolTaskExecutor e = new ThreadPoolTaskExecutor();
        e.setCorePoolSize(8); e.setMaxPoolSize(32);
        e.setQueueCapacity(2000);
        e.setThreadNamePrefix("biz-");
        e.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        e.setTaskDecorator(new MdcTaskDecorator());   // ★ 传递 TraceId,否则异步日志断链
        e.initialize();
        return e;
    }
}
// 使用:@Async("bizExecutor")   一定要指定线程池名,别用默认

@Async 的三个坑:必须指定线程池(默认 SimpleAsyncTaskExecutor 每次新建线程)、不能同类自调用(同事务)、要自己做 TraceId 传递。

四、生产配置清单(application.yml 关键项)

server:
  shutdown: graceful              # 优雅停机,配合下面的超时
  tomcat:
    threads:
      max: 400                    # 默认 200,高并发下不够
    connection-timeout: 20000
    max-swallow-size: -1
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
  mvc:
    throw-exception-if-no-handler-found: true
  jackson:
    default-property-inclusion: non_null     # 返回体不吐 null 字段
    time-zone: Asia/Shanghai
  datasource:
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000                  # ★ 要小于 MySQL 的 wait_timeout
      leak-detection-threshold: 60000        # 连接泄漏检测,开发环境必开
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: never                    # ★ 公网不要暴露 details

max-lifetime 一定要小于 MySQL 的 wait_timeout(默认 8 小时),否则连接池里会出现已被服务端断开的死连接。

五、配置不生效 / 启动慢的排查

现象 排查
配置改了没生效 application.yml 加载顺序:命令行 > 环境变量 > application-{profile}.yml > application.yml;确认 spring.profiles.active
不知道哪个配置生效 Actuator 的 /actuator/env + /actuator/configprops
自动配置到底开没开 --debug 看 CONDITION EVALUATION DELTA;或 /actuator/conditions
启动慢 加 -Dspring.main.lazy-initialization=true 验证是否 Bean 太多;用 SpringStartupAnalyzer 生成启动耗时火焰图
端口被占 netstat -tlnp \| grep 8080 / lsof -i:8080

六、打包与启动命令

mvn clean package -DskipTests
java -jar app.jar --spring.profiles.active=prod
# 指定内存与 GC
java -Xms2g -Xmx2g -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -jar app.jar
# 后台运行并留日志
nohup java -jar app.jar > /data/logs/app.log 2>&1 &
# 优雅停机(先摘流量,再 kill)
kill -TERM <pid>        # 不要用 kill -9

kill -9 是禁用项:正在处理的请求直接断、事务来不及提交、优雅停机逻辑完全不执行。正确顺序是「注册中心/网关摘流量 → 等待 30s → kill -TERM」。

七、自定义 starter(团队基建常用)

做一个内部 starter 只需要三步:

  1. xxx-spring-boot-autoconfigure 模块里写 @Configuration + @ConditionalOnXxx
  2. META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 里写上配置类全限定名
  3. 再包一个 xxx-spring-boot-starter 只做依赖聚合

内部中间件(统一日志、TraceId、多租户上下文)做成 starter,比每个服务复制代码靠谱得多。

— 感谢读到这里 —发现错误,或想补充?告诉我 →
TURN KNOWLEDGE INTO SOMETHING REAL

有想法,也想把它做出来?

从技术方案到上线交付,聊聊你的下一个项目。

聊聊需求

一起聊聊

搜索文章

输入关键词,按标题和摘要即时筛选。

浏览分类文章归档

阅读设置

字号
行宽
专注模式

专注模式会隐藏页头与侧栏,只留正文。设置保存在当前浏览器。

稍后阅读

保存在当前浏览器,留给下一次安静阅读。

总浏览 -- 次 · 总访客 -- 人 · 今日浏览 -- 次 · 今日访客 -- 人