Spring Boot 项目结构的几个习惯
用 Spring Boot 做后端时,项目结构看似自由——官方文档也不会强制你用什么目录名。但一个人写和三个人写,完全是两种体验。团队一旦扩大,「约定」比「灵活」更重要:新人clone 下来,应该不用问「这个类放哪」就能开始干活。
下面是我会在项目里坚持的几个习惯。不追求大而全的架构,只解决日常开发里反复出现的摩擦。
分层清晰,职责单一
典型的调用链是 controller → service → mapper/repository,三层不要混用:
- Controller: 接收请求、参数校验、调用 Service、封装响应。不写业务逻辑,不直接操作数据库
- Service: 业务编排、事务边界、领域规则。一个 Service 方法对应一个明确的业务动作
- Mapper / Repository: 数据访问。SQL 或 JPA 查询收敛在这里,不散落在 Service 里拼字符串
违反这条最简单的信号是:Controller 里出现 if/else 业务分支,或者 Service 里直接 return ResponseEntity。看到这种代码,通常意味着该拆了。
包结构按业务划分
除了按技术层分包(controller、service、mapper),我更倾向在每一层里再按业务模块划分子包:
com.example.project
├── user
│ ├── UserController
│ ├── UserService
│ └── UserMapper
├── order
│ ├── OrderController
│ └── ...
└── common
├── exception
└── config好处是改「用户模块」时,改动范围基本锁在 user/ 目录里,Code Review 和冲突概率都更低。common/ 只放真正跨模块共享的东西,避免变成杂物间。
配置外置,环境可切换
数据库连接、Redis 地址、第三方 API Key、日志级别——全部放进 application.yml 和 application-{profile}.yml,通过 spring.profiles.active 切换。
几条硬规矩:
- 密码和密钥绝不提交到 Git,用环境变量或配置中心注入
- 本地开发用
devprofile,测试环境test,生产prod——配置差异只体现在 profile 文件里 - 魔法数字(超时时间、分页大小)也写进配置,不要散落在代码常量里
Spring Boot 的 @ConfigurationProperties 很适合把一组相关配置绑成一个类型安全的类,比满项目 @Value("${xxx}") 好维护得多。
统一异常处理
用 @RestControllerAdvice 集中处理业务异常、参数校验失败和未捕获错误,返回统一的 JSON 结构:
{ "code": 40001, "message": "用户不存在", "data": null }业务层抛自定义异常(比如 BusinessException),携带错误码和提示信息;Advice 负责映射成 HTTP 状态码和响应体。前端对接时只需要认一种错误格式,不用猜每个接口返回的字段名。
参数校验用 @Valid + @NotBlank 等注解,校验失败同样走统一处理,返回字段级的错误提示。
DTO 与实体分离
数据库实体(Entity)不要直接暴露给前端。至少准备两层对象:
- Request DTO: 接收入参,带校验注解
- Response VO: 返回给前端,只包含需要的字段,隐藏内部 ID 和敏感信息
实体和 DTO 之间的转换可以用 MapStruct 或手写 mapper,别在 Controller 里一个个 set。字段一多,手动赋值迟早出错。
接口文档不要省
集成 SpringDoc(OpenAPI 3)或 Swagger,让接口在开发阶段就能可视化调试。个人项目可以轻量化,但以下不要省略:
- 每个接口的用途说明(
@Operation) - 请求/响应字段的含义和示例值
- 错误码对照表
文档不是给别人看的——三个月后的你自己,就是文档的最大受益者。
日志与可观测性
几条实用习惯:
- 入口日志记录关键参数(脱敏后),出口记录耗时和结果摘要
- 业务异常用
warn,未预期异常用error并打印堆栈 - 给每个请求加 traceId(拦截器生成),方便串联日志
别在循环里打 info,别把整个对象 JSON 序列化进日志——磁盘和 ELK 都会感谢你。
测试策略
不追求 100% 覆盖率,但以下必须有:
- Service 层核心逻辑的单元测试(Mock 掉 Mapper)
- 关键接口的集成测试(
@SpringBootTest+ 测试数据库) - 参数校验边界 case(空值、超长、非法格式)
测试代码也是项目结构的一部分,放在 src/test 下镜像主代码的包结构,找起来不费劲。
好的项目结构不是约束创造力,而是减少重复决策的消耗。
这些习惯不华丽,但能在项目从「能跑」走向「能维护」的过程中省掉大量沟通成本。如果你也在用 Spring Boot 搭后端,挑几条适合自己的坚持下去,比一次性套用某种「标准架构」更实际。