
从第一次用 Spring Boot 到现在我最深的感受是这个框架真正把能让项目跑起来的门槛拉低了一大截。Spring Boot 项目开发的全流程说白了就是初始化工程、写好配置、把业务按模块拆开、再把日志缓存和通信这些横切点一个个接进去。这篇文章不是什么官方文档翻译而是我把这些年做 Spring Boot 项目的经验从第一个程序到集成 WebSocket、gRPC、接入缓存和安全配置迁移整理成一套可以照着落地的实战思路。适合刚学完 Java 基础、准备上手第一个 Spring Boot 项目的同学也适合天天写业务但很少研究配置细节的开发者甚至做技术选型时都能拿来做清单对照。1. 先搞清楚Spring Boot 到底是个什么容器1.1 Spring、Spring Boot、Spring Cloud 微服务别再把它们搅在一起我发现很多人做了大半年项目还是分不清 Spring、Spring Boot、Spring Cloud 有什么区别。有一次面试候选人简历里写着熟悉微服务结果问 Spring Boot 和 Spring Cloud 的关系他直接说就是同一个东西。其实这三者的定位差异非常大理解错了后面做全流程开发会走很多弯路。Spring 是一个生态核心是 Spring Framework提供 IoC 容器、AOP、事务管理这些基础能力。Spring Boot 是建立在 Spring Framework 之上的启动器和自动配置层它解决的问题不是帮你写业务而是让 Spring 项目的初始化、依赖管理、运行方式变得极其简单。Spring Cloud 则是微服务场景下的一整套分布式解决方案包括服务注册发现、配置中心、网关、熔断限流等。可以这样类比Spring Framework 是一台发动机Spring Boot 是一键启动装置而 Spring Cloud 是车队调度系统。发动机负责提供动力一键启动让点火变得无脑车队调度系统则负责多辆车协同工作。三者的关系用一张表可以看得很清楚层面核心定位典型组件解决的问题Spring Framework基础框架IoC、AOP、事务、MVC对象管理和企业级能力Spring Boot快速开发脚手架Starter、自动配置、内嵌容器省去大量 XML 和手动装配Spring Cloud微服务治理套件Nacos、Gateway、OpenFeign分布式系统的通信与治理要注意的是Spring Boot 不等于微服务。哪怕你只做一个单机应用Spring Boot 也完全够用而且很合适。很多团队上来就上微服务其实是过度设计单体应用用 Spring Boot 加模块化分包前期开发效率和运维成本都低得多。1.2 全流程开发中约定大于配置到底省了什么Spring Boot 最大的贡献是约定大于配置。什么意思就是说你遵循它的默认约定就可以少写大量配置。举个例子以前用 Spring MVC 搭一个 Web 项目需要手动配置 DispatcherServlet、视图解析器、数据源、事务管理器光是 XML 就能写几百行。Spring Boot 只要引入spring-boot-starter-web内嵌 Tomcat 自动启动Controller 一写就能访问默认约定全部生效。这套机制靠的是SpringBootApplication注解背后的三个核心注解SpringBootConfiguration标记配置类EnableAutoConfiguration开启自动配置ComponentScan扫描当前包及子包的组件。自动配置会根据你的 classpath 推断要装配什么。比如你引入了spring-boot-starter-data-redis它就在RedisAutoConfiguration里为你创建一个连接工厂和 RedisTemplate你引入mybatis-spring-boot-starter它就会帮你配置 SqlSessionFactory。这种看菜下饭、缺什么配什么的设计让全流程开发的重心从搭环境转移到写业务。我自己的经验是Spring Boot 3 之后自动配置的机制越来越规范AutoConfiguration.imports文件取代了旧的spring.factories如果你需要自定义自动配置直接去看META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports这个文件就能快速定位。这个细节在排查为什么我的配置没生效时特别有用。不过约定大于配置也有副作用就是当约定和你的实际需求不一致时排查起来会比以前更难受因为很多东西是隐式生效的。我后面会专门讲配置排查的思路。2. 项目初始化从零到第一个接口跑起来2.1 快速创建项目的三种姿势Spring Boot 项目初始化非常快这也是它让新手最有成就感的一点。常见的创建方式有三种我分别说下优缺点第一种IDEA 内置 Spring Initializr。新建项目时选择 Spring Initializr填好 Group、Artifact勾选需要的依赖点 Finish一个可运行的项目就生成了。这是最推荐的方式因为它把依赖树直接固化到 pom.xml后续不用担心依赖缺漏。注意在选择 Spring Boot 版本时不要一味追求最新版看看你要用的中间件是否兼容。比如 Spring Boot 3.0 刚发布时很多第三方 starter 还没适配现在虽然好多了但版本兼容性依然是第一优先级。第二种start.spring.io 网页生成。和 IDEA 里的初始化逻辑一样适合需要在不同机器上反复创建项目的场景。我的习惯是在网页上选好依赖下载 zip解压后用 IDEA 打开。这样做的好处是方便把同一个初始化配置分享给团队其他人避免每个人创建的工程千奇百怪。第三种纯手工创建 Maven 工程再引入父 POM。这个是应急方案适合了解底层原理的情况。核心就是在 pom.xml 中引入spring-boot-starter-parent作为父工程再添加需要的 starter 依赖。手工创建虽然麻烦但对理解 Spring Boot 项目结构非常有帮助尤其是后面处理版本冲突时你会感谢自己知道父 POM 是怎么工作的。实操中最常见的坑是IDEA 初次打开项目时没有自动加载 Maven 依赖。解决方法是在 IDEA 右侧 Maven 面板点刷新或者执行mvn clean compile。还有一个小细节创建好项目后第一件事是改maven-compiler-plugin的 Java 版本确保和本机 JDK 一致否则编译直接报错。2.2 目录结构与启动类的讲究一个标准的 Spring Boot 项目目录结构长这样com.example.demo ├── DemoApplication.java ├── controller ├── service │ └── impl ├── repository ├── entity ├── config ├── dto └── common这里的核心约定是启动类DemoApplication必须放在根包下。因为SpringBootApplication里的ComponentScan默认扫描启动类所在包及其子包。如果你把启动类放在com.example.start而 Controller 放在com.example.controllerSpring 扫描不到接口 404这种情况我见过太多次了。启动类本身也非常简单核心就是一个 main 方法加SpringBootApplication。如果你想在启动时做点初始化操作比如打印 banner、执行数据预加载可以实现ApplicationRunner或CommandLineRunner把初始化代码放到run方法里。我自己偏好ApplicationRunner因为它的参数已经解析成ApplicationArguments不用再自己做命令行解析。这里顺便说下第一个 Spring Boot 程序通常的验收标准启动无报错、访问http://localhost:8080/hello能返回 JSON、日志里能看到内嵌 Tomcat 启动信息。只要这三条通过说明你的环境、依赖、启动类都没有问题。2.3 Bean 注入控制基础但坑最多Spring Boot 全流程开发中Bean 的创建和注入是最基础也最容易翻车的点。很多人只会用Autowired碰到循环依赖、同类型多实现时就开始头皮发麻。先说注入方式的取舍。Autowired是类型注入遇到同类型多个 Bean 时会报NoUniqueBeanDefinitionException。解决的常规做法是搭配Qualifier(beanName)或者在Resource(name ...)里指定名字。构造器注入是我最推荐的Spring 官方也建议用构造器注入因为能保证依赖不可变、方便单元测试。Spring Boot 3 里如果只有一个构造器连Autowired都不用写。Bean 作用域也经常被忽略。默认是单例Scope(prototype)每次获取都是新实例。做状态管理时如果误把有状态对象声明为单例并发场景就会出现数据串烧。比如一个计数器 Bean如果在单例里存了可变字段多线程环境下数据就是乱的。再就是 Spring Boot 2.6 之后默认禁止循环依赖。以前用Autowired偷偷绕过去的方式在新版本里直接报错。真正的解法是重构依赖关系把公共逻辑抽到独立 Service 里或者用事件监听解耦。比如 A 服务调用 B 服务B 又需要 A就应该考虑把两者共同依赖的领域逻辑下沉到第三层而不是硬靠 Spring 的循环依赖能力。我用地址簿管理这个经典案例写过一个最小的依赖注入示例结构是Service public class AddressBookService { private final AddressRepository repository; public AddressBookService(AddressRepository repository) { this.repository repository; } public Address save(Address address) { return repository.save(address); } }这里AddressRepository由 Spring Data JPA 自动生成代理实现构造器注入完成装配。整个流程里没有一行 XML没有Autowired字段但依赖关系清清楚楚。这就是 Spring Boot 全流程开发里的标准动作。3. 业务系统设计地址簿、商城、办公用品管理都长一个样3.1 这类业务系统的共性拆解我看过很多 Spring Boot 设计题题目比如基于 Spring Boot 的企业办公用品管理系统的设计与实现Spring Boot 设计题目商城使用 Spring Boot 编写地址簿管理这类业务系统摆在技术栈上要做的事本质其实是高度相似的。它们几乎都遵循同一个模式实体管理 权限控制 业务流程状态化 简单报表。办公用品管理系统核心实体是办公用品、领用申请、库存流水商城系统的核心实体是商品、购物车、订单、支付流水地址簿管理的核心实体是联系人、分组。无论业务名词怎么变底层都是Entity Repository Service Controller这一套。把这套模式练熟再接到什么业务都能快速上手。以下面这个地址簿管理为例我建议的落地结构是Entity public class Contact { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String name; private String phone; private String address; private String groupName; // getter/setter 省略 }然后ContactRepository extends JpaRepositoryContact, Long在 Service 层写增删改查逻辑Controller 层暴露 REST API。整个过程没有复杂的架构但这就是全流程开发的第一版能用的系统。3.2 QueryDSL 与 Spring Boot 版本对应关系做商城或办公用品这类管理系统时查询条件往往特别多商品按价格区间查、按分类查、按模糊名称查。如果只用 JPA 方法名推导方法会爆炸如果用原生 SQL类型不安全。这时很多团队会引入 QueryDSL。最近有人问我io.github.openfeign.querydsl这个依赖和 Spring Boot 版本的对应关系。实际上这是个容易混淆的点。openfeign.querydsl 的命名确实有迷惑性它并不是 OpenFeign 官方库而是社区里在 Spring Boot 环境下整合 QueryDSL 的替代坐标。传统上是使用querydsl-apt配合注解处理器来生成QClass但在 Spring Boot 3 / Jakarta 命名空间之后版本对应经常出问题所以出现了这类重新打包的坐标。Spring Boot 版本推荐的 QueryDSL 方式说明2.xquerydsl-jpa 4.x querydsl-apt使用 javax.persistence3.0.xquerydsl-jpa 5.x 官方 apt需要 Jakarta 适配3.2JPA Criteria / QueryDSL 6.x视项目情况社区兼容包可能更省事我的建议是如果项目里查询复杂度不高优先用 Spring Data JPA 的 Specification它是官方方案版本永远跟 Spring Boot 走不要为了 QueryDSL 引入额外的版本地狱。如果确实要引入 QueryDSL一定要先确认jakarta.persistence和querydsl-apt的版本是否匹配否则编译阶段生成不了 QClass运行期必然报错。还有一种思路是直接不在 JPA 里做复杂查询而是用 MyBatis 或 JdbcTemplate。所有复杂查询统一走 SQL 文件使用Mapper扫描。这样既绕开了 QueryDSL 的版本问题也更容易做 SQL 优化。很多老项目到后期都走这条路。3.3 餐饮 SaaS 与 AI 集成的新场景最近的网络热词里有一个组合很有意思Spring Boot 餐饮 SaaS AI 集成。这说明 Spring Boot 的业务场景已经不只是传统 CRUD开始往业务 AI 能力方向延伸。餐饮 SaaS 里常见的 AI 集成场景包括智能菜单推荐、食材采购预测、顾客评价情感分析、客服自动回复。从 Spring Boot 项目开发的角度看接入 AI 服务本质上是一次 HTTP/RPC 调用和调用第三方开放接口没有本质区别。你可以用RestClient或WebClient调用大模型服务商的 API把业务数据拼装成 prompt再把结果解析回 DTO。我自己的建议是分两步走第一步先做旁路集成即 AI 能力不影响主交易链路作为一个辅助服务存在。比如菜单推荐是独立的接口推荐失败不影响下单。第二步再考虑把 AI 输出结果落到数据库供运营后台使用。不要一上来就试图让 AI 参与订单状态机既不稳定也不可控。这个思路放到任何传统业务 新能力的集成里都适用。4. YML 配置与 WebSocket 集成实操4.1 application.yml拆文件、上环境Spring Boot 的配置核心是application.yml但一个成熟的项目不会把所有配置塞在一个文件里。我习惯拆成这样application.yml公共配置比如应用名、端口、编码。application-dev.yml开发环境比如本地数据库、debug 日志。application-prod.yml生产环境配置加密后的密码、生产日志级别。用spring.profiles.active指定当前生效的环境。实际操作中我更喜欢直接用环境变量覆盖比如启动参数--spring.profiles.activeprod或者环境变量SPRING_PROFILES_ACTIVEprod。这样同一个 jar 包在哪个环境跑就激活哪套配置不用重新打包。YML 书写时最容易犯的错误是缩进。YAML 对缩进极其敏感空格数量错了配置文件就直接解析失败。另一个坑是数组写法my: whitelist: - 192.168.0.1 - 192.168.0.2注意-前面要有空格项目符号下的属性也要对齐。使用ConfigurationProperties读取自定义配置时类名要加ConfigurationProperties(prefix my)并且在启动类或配置类上加EnableConfigurationProperties或者在类上加上Component。很多人说我配置了怎么读不到八成是没触发属性绑定。4.2 WebSocket 为什么要在 YML 里配Spring Boot 集成 WebSocket很多人只盯着 Controller 里的ServerEndpoint注解忽略了 YML 配置。实际上如果用 STOMP 协议需要配置的点位还真不少。一个比较典型的配置如下server: port: 8080 spring: websocket: stomp: broker-relay: enabled: false不过说实话Spring Boot 官方对 WebSocket 的 YML 配置项很少大部分逻辑要在代码配置类里设置这也是这个主题下面很多人困惑的点。我建议你在application.yml里至少放三类参数连接超时时间、心跳间隔、消息缓存上限。因为 WebSocket 连接是长连接服务器端需要控制心跳和超时否则连接多了以后内存和线程都会被拖垮。一个最小的 WebSocket 配置类是这样的Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(/ws) .setAllowedOriginPatterns(*) .withSockJS(); } Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker(/topic, /queue); registry.setApplicationDestinationPrefixes(/app); } }这段配置的意思是客户端连接地址是/ws消息代理处理/topic和/queue前缀客户端发送到服务端的消息以/app开头。配合MessageMapping(/hello)就能实现一个完整的 WebSocket 接口。4.3 集成时的常见坑走一遍真实踩坑我在 WebSocket 上踩过的坑比 REST 多了不止一倍。第一个坑是跨域。setAllowedOrigins(*)在老版本里可以Spring Boot 3 里setAllowedOriginPatterns(*)才能配合 allowCredentials(true) 使用。前端连不上时第一反应要在浏览器 Network 面板看 WebSocket 握手请求是失败还是被拒绝。第二个坑是心跳与断线重连。STOMP 的心跳由客户端和服务端协商如果一端配置了心跳而另一端没有连接会频繁断开。我的做法是前后端统一约定心跳间隔 25 秒超过 60 秒没有消息就主动重连。服务端记得要写一个WebSocketHandler或监听 session 断开事件把用户会话从在线列表里移除否则会累积僵尸连接。第三个坑是集群部署。如果项目要水平扩展WebSocket 就不能用单机enableSimpleBroker因为用户 A 连在节点 1消息从节点 2 推送时它收不到。这个场景下需要集成外部消息代理比如 RabbitMQ 的 STOMP 插件。这一点在做 SaaS 系统时几乎避不开所以建议一开始就评估连接量和部署方式。5. 日志、缓存、gRPC性能与可观测性5.1 Spring Boot 日志你得倒底会多少Spring Boot 默认用 Logback 作为日志框架一般人知道改logging.level就觉得自己会日志了其实离会还很远。全流程开发里日志至少要覆盖三件事分级输出、滚动策略、链路追踪。logback-spring.xml是我每个项目都会放的文件。核心配置包括 ConsoleAppender 和 RollingFileAppender。滚动策略我用SizeAndTimeBasedRollingPolicy按天切分同时限制单文件大小比如每个文件最多 200MB保留 30 天appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/app.log/file rollingPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy fileNamePatternlogs/app.%d{yyyy-MM-dd}.%i.log/fileNamePattern maxFileSize200MB/maxFileSize maxHistory30/maxHistory /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender还有一个容易忽略的是异步日志。日志写入磁盘如果同步执行在高并发下会严重影响接口响应。Logback 的AsyncAppender可以解决这个问题但注意配置队列深度和丢弃策略别让日志队列本身变成内存泄漏。生产环境我一般设置queueSize8192丢日志比例控制在 1% 以内。MDC 用于链路追踪非常实用。可以在拦截器里把 requestId 放进MDC.put(requestId, UUID.randomUUID().toString())然后在 logback pattern 里加上[%X{requestId}]。这样打出来的每一行日志都带请求 ID排查线上问题时按这个 ID 一拉整条调用链都在面前。5.2 Caffeine 本地缓存比 Redis 更快的一层Spring Boot Caffeine的组合几乎是本地缓存的标准答案。Caffeine 基于 Java 的高性能缓存库性能比旧版 Guava Cache 高不少而且和 Spring Cache 抽象无缝集成。很多项目一上来就上 Redis完全忽略了进程内本地缓存这层。实际上对于热点数据比如餐饮 SaaS 里的菜品列表、商城里的商品详情直接走 Caffeine 比走一次 Redis 要快一个数量级。Redis 适合多实例共享数据Caffeine 适合单机热点加速两者不冲突常见做法是 Caffeine 做一级缓存Redis 做二级缓存。在 Spring Boot 里集成 Caffeine 只需要三步。第一步引入依赖dependency groupIdcom.github.ben-manes.caffeine/groupId artifactIdcaffeine/artifactId /dependency第二步写配置类Configuration public class CacheConfig { Bean public CacheManager cacheManager() { CaffeineCacheManager manager new CaffeineCacheManager(); manager.setCaffeine(Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(Duration.ofMinutes(10))); return manager; } }第三步在方法上使用Cacheable(value menu, key #storeId)。实战中的坑是缓存穿透。恶意请求用不存在的 ID 反复查询Caffeine 默认不会缓存 null 值结果每次都打到数据库。我的做法是缓存空对象标记或者用Optional包装结果。另外缓存失效时要防止多个线程同时回源Caffeine 内部对单个 key 的加载有原子性保护但如果用Cacheable配合外部 DB还是需要留意缓存击穿。可以用Cacheable(sync true)来开启 synchronized 回源。5.3 gRPC 协议与 Spring Boot 集成方式grpc 协议 spring boot也是近期热词。很多项目内部服务间通信还在用 REST JSON但到了高并发场景gRPC 的优势非常明显基于 HTTP/2 多路复用、使用 Protobuf 二进制序列化、性能远好于 JSON、自带流式通信。代价是要管理.proto文件和生成代码链路比 REST 复杂。Spring Boot 集成 gRPC社区常用的库是net.devh:grpc-server-spring-boot-starter和grpc-client-spring-boot-starter。写法上服务端只需要GrpcService public class UserGrpcService extends UserServiceGrpc.UserServiceImplBase { Override public void getUser(GetUserRequest request, StreamObserverGetUserResponse responseObserver) { // 业务逻辑 } }客户端用GrpcClient(user-service)注入 stub然后像调用本地方法一样调用远程服务。我的建议是不要一上来就给所有接口上 gRPC。服务对外 API 用 REST方便外部系统接入服务间高吞吐、低延迟调用用 gRPC比如订单服务调库存服务。只有连接数量多、载荷大、对延迟有硬性要求的场景才值得引入。还有一点Protobuf 的字段编号一旦发布就尽量不要改动这是兼容性红线删字段要用reserved标记否则新旧服务混跑会出解析问题。6. Spring Boot 3 安全配置迁移从 Security 5 到 Security 66.1 你可能遇到的改法都不一样Spring Boot 3 对应的 Spring Security 6 对比 5 变化非常大。很多老项目升级时编译能过但运行期一堆 403甚至直接启动失败。最大的变化之一是 lambda DSL 成为强制写法旧的antMatchers方法被彻底移除换成requestMatchers。举个例子Spring Security 5 时代的写法http.authorizeRequests() .antMatchers(/admin/**).hasRole(ADMIN) .antMatchers(/public/**).permitAll() .anyRequest().authenticated();Spring Security 6 里要改成http.authorizeHttpRequests(auth - auth .requestMatchers(/admin/**).hasRole(ADMIN) .requestMatchers(/public/**).permitAll() .anyRequest().authenticated());方法名变了DSL 风格也变了。如果不熟悉新写法升级后第一个报错就是authorizeRequests不存在。另一个大坑是 CSRF 的默认策略。Spring Security 6 默认开启 CSRF 防护如果你用 Postman 测试 POST/DELETE 接口会一直 403。要么在配置里显式关闭csrf(csrf - csrf.disable())要么在前端请求里带上 CSRF Token。从开发效率角度前后端分离项目通常选择在服务端配置放行无状态请求但我建议你理解清楚再关不要无脑关掉。6.2 迁移注意清单我自己帮团队迁过一个 Spring Boot 2.7 到 3.1 的项目总结了一份检查清单照着核对基本不会漏密码编码器{noop}明文前缀在新版本依旧可以用但强烈建议换BCryptPasswordEncoder写法也要显式定义 BeanWebSecurityConfigurerAdapter已经彻底删了不要再继承它。自定义过滤器以前放入HttpSecurity的addFilterBefore写起来很绕新版本支持addFilterBefore同名方法但类型参数要写对否则过滤器不生效。OAuth2 与 JWTJwtSecurityConfigurer和相关包从spring-security-oauth2-resource-server来引入依赖时确认版本是 6.x。CORSSpring Security 的 CORS 和 MVC 的 CORS 是两套要同时配置。经常出现直接访问 Controller 能通、走 Security 就 403的诡异问题其实就是 security 链路里 CORS 没配。默认登录页如果你的项目不是前后端分离Spring Security 6 默认生成的登录页也可能受 CSRF 影响需要注意登录请求带上 Token。这里插一句Spring Security 配置迁移本质上就是旧的继承体系转为全新的组件式配置所以不要试图迁移而是当成重新写一遍安全配置。你只需要保留密码加密方式和 Token 校验逻辑其余代码推倒重写反而比修修补补更快。7. 全流程开发中的常见问题排查7.1 启动失败速查表平时群里问得最多的还是启动报错。我整理一个速查表基本覆盖高频问题报错/现象大概率原因解决方向Port 8080 already in use端口被占用改端口或查占用进程The bean xxx could not be injectedBean 未注册或类型冲突检查包扫描路径和注解Consider defining a bean of type xxx依赖缺失或未被扫描确认 Mapper/Service 包路径Failed to configure a DataSource没有配置数据源引入数据库依赖并配置连接Circular dependency detected循环依赖重构依赖关系ClassNotFound / NoClassDefFoundjar 包冲突或缺失用 dependency:tree 分析启动类放在错误的包路径导致所有组件扫描不到也是极常见的翻车点。解决办法只有一个让启动类位于根包或显式指定scanBasePackages。项目后期模块多了以后我反而更推荐启动类里显式写scanBasePackages com.example可以防止新增模块时组件莫名消失。7.2 配置不生效与约定优先的误区配置问题比启动失败更难排查因为程序能跑只是行为不对。比如你明明在 application.yml 里写了server.port: 8081启动还是 8080很可能是application.properties和application.yml同时存在而前者的优先级更高覆盖了 YML 里的配置。Spring Boot 的配置加载优先级顺序是个经典考点遇到配置不生效先按优先级从高到低捋一遍命令行参数、环境变量、application-{profile}.yml、application.yml、默认配置。另一个高发坑是Value取不到值。Value(${my.config})只有在当前类被 Spring 管理时才有效。如果你在new出来的普通类里用Value永远拿不到值。正确做法是把类注册为 Bean或者把配置封装成ConfigurationProperties的数据类再注入。环境变量占位符的写法也值得注意。比如spring.datasource.password: ${DB_PASSWORD:root}表示优先取环境变量DB_PASSWORD取不到就用默认值root。这种写法让配置在开发和生产之间平滑切换但要注意默认值别写生产密码否则可能泄露到代码仓库里。我现在在团队推行的规范是本地默认值只允许填本地开发环境的值生产密码一律从部署平台注入。7.3 排查思路先看日志再看线程再看依赖遇到一个让人摸不着头脑的线上问题我的排查顺序是固定的。第一步先看日志。看有没有明显的ERROR、WARN和异常堆栈。第二步看线程状态。用jstack导线程快照看有没有BLOCKED、WAITING堆积高并发常见的就是线程池耗尽和数据库连接池耗尽。第三步看依赖。执行mvn dependency:tree检查有没有重复或冲突的 jar 包。有一次接口偶发超时日志里什么异常都没有最后发现是连接池参数没配默认的 HikariCP 最大连接数 10并发一高全部等连接。修改spring.datasource.hikari.maximum-pool-size后问题解决。这个例子说明很多性能问题不会直接在日志里暴露需要检查默认参数是否匹配业务量。还会用 Spring Boot Actuator 作为第一道检查工具。加spring-boot-starter-actuator依赖后访问/actuator/health就能看应用健康状态/actuator/metrics能看到 JVM 内存和线程数据。不过生产环境要记得通过management.server.port和management.endpoint.*配置限制暴露范围不能直接把所有端点裸奔到公网。另外我建议每个项目都保留一个--debug启动模式的口味。Spring Boot 支持--debug参数输出自动配置的审计日志启动时能看到Positive matches和Negative matches。排查自动配置类为什么没生效时这个输出能直接告诉我们某个条件没满足非常高效。排查完了把参数去掉再正常启动。最后再分享一个很实在的技巧遇到看不太懂的报错先不要急着搜错误信息而是回到项目里找出是哪个 starter 引入的、哪个自动配置类参与的。理解了 Spring Boot 的自动配置机制后绝大多数启动和配置问题其实都能靠这个思路自己解决比在网上搜来路不明的通用解法要可靠得多。我在实际项目里养成的习惯是每个人都要能读懂启动日志里前 50 行那里包含了版本、自动配置和初始化状态排查问题一半以上的答案都写在里面了。