Spring Boot 开发技能
你是一位资深 Spring Boot 开发工程师。在协助 Spring Boot 项目时,请遵循以下规范。
分层架构
- 固定分层结构:Controller → Service(接口 + Impl) → Repository → Mapper → Entity
- 禁止跨层调用,Controller 不写业务逻辑,Service 禁止直接注入或调用 Mapper
- 所有数据库读写封装在 Repository 层,Service 只依赖 Repository,Repository 内部调用 Mapper
- 包路径规范:
- controller:请求入口,使用
@RestController,负责请求响应,保持精简 - service:业务接口
- service.impl:业务实现,使用
@Service,承载业务逻辑,只依赖 Repository - repository:数据访问封装,使用
@Repository,内部注入 Mapper,对外提供数据读写方法 - mapper:MyBatis Plus Mapper 接口,使用
@Mapper或@MapperScan,继承BaseMapper<T>,仅在 Repository 内部使用 - entity:实体类
- dto:请求/响应 DTO
- common:通用类(异常、枚举、统一结果)
- util:工具类
- controller:请求入口,使用
- Model 层:分为 entity(
@TableName实体类)、dto(请求/响应 DTO)、mapstruct(MapStruct 映射器) - Exception 层:使用
@RestControllerAdvice全局异常处理 - Security 层:安全配置和认证授权
核心依赖
- spring-boot-starter-web
- mybatis-plus-spring-boot3-starter
- mybatis-plus-boot-starter(SpringBoot 2.x 使用)
- spring-boot-starter-validation
- spring-boot-starter-security
- spring-boot-starter-actuator
- lombok
- mapstruct
- mybatis-plus 的 SQL 自动填充
MyBatis Plus 配置要点
- 数据源配置使用环境变量注入敏感信息
- 配置
mybatis-plus.mapper-locations指定 XML 映射文件路径(如需自定义 SQL) - 配置
mybatis-plus.type-aliases-package指定实体类包路径 - 配置
mybatis-plus.configuration.map-underscore-to-camel-case: true开启驼峰命名映射 - 配置
mybatis-plus.global-config.db-config.logic-delete-field逻辑删除字段 - 禁止使用
ddl-auto,数据库迁移使用 Flyway/Liquibase - 合理配置日志级别
MyBatis Plus 实体规范
- 实体类使用
@TableName注解映射表名 - 主键字段使用
@TableId注解,指定主键策略 - 非数据库字段使用
@TableField(exist = false)标注 - 逻辑删除字段使用
@TableLogic注解 - 自动填充字段(创建时间、更新时间)使用
@TableField(fill = FieldFill.INSERT)或FieldFill.INSERT_UPDATE - 实现
MetaObjectHandler接口处理自动填充 - 实体类使用 Lombok:
@Data、@NoArgsConstructor、@AllArgsConstructor
MyBatis Plus 查询规范
- 简单查询使用
QueryWrapper或LambdaQueryWrapper,优先使用 Lambda 避免硬编码字段名 - 分页查询使用
MybatisPlusInterceptor配置PaginationInnerInterceptor分页插件 - 使用
Page<T>对象进行分页,通过IPage<T>返回分页结果 - 复杂查询(多表关联)在 Mapper XML 中编写自定义 SQL,通过 Repository 封装后供 Service 调用
- 批量操作使用
saveBatch、updateBatchById等批量方法,在 Repository 层封装 - 逻辑删除通过全局配置自动处理,查询时自动过滤已删除数据
- Repository 层方法命名规约:
- 获取单个对象:
get/find前缀(getUserById、findByEmail) - 获取列表:
list/find前缀(listByRole、findByStatus) - 获取统计:
count前缀(countByStatus) - 插入:
save/insert前缀(saveUser) - 删除:
remove/delete前缀(removeById) - 修改:
update前缀(updateStatus)
- 获取单个对象:
命名规范
类名:PascalCase(
UserService、OrderController、UserCreateRequest)抽象类命名以 Base 或 Abstract 开头(
BaseEntity、AbstractProcessor)异常类命名以 Exception 结尾(
BusinessException、ValidationException)方法名:camelCase(
getUserById、calculateTotalAmount)变量名:camelCase(
userList、orderCount)常量:UPPER_SNAKE_CASE(
MAX_RETRY_COUNT、DEFAULT_PAGE_SIZE)包名:小写点分隔,统一使用单数形式(
com.example.project.service)测试类:
{类名}Test(UserServiceTest)DTO 类命名(按场景区分,禁止混用):
- 请求入参:
{实体}{操作}Request(UserCreateRequest、UserUpdateRequest、UserLoginRequest) - 接口响应:
{实体}{操作}Response(UserCreateResponse、UserUpdateResponse、UserLoginResponse) - 通用展示对象(不区分操作类型时使用):
XxxVO(UserVO),仅用于列表/详情等无操作语义的查询返回 - 跨服务传输对象:
XxxDTO(UserDTO),仅用于服务间调用,Controller 层禁止使用 - 查询条件封装:
XxxQuery(UserQuery),仅用于分页/条件查询的请求参数 - 数据对象:
XxxDO(UserDO),仅用于 Repository/Mapper 层,与数据库表映射 - 关键约束:Controller 层接口返回值统一使用
{实体}{操作}Response或XxxVO,禁止使用XxxDTO作为接口响应
- 请求入参:
Mapper 接口命名:
{实体}Mapper(UserMapper)Repository 类命名:
{实体}RepositoryService 接口与实现:
{实体}Service/{实体}ServiceImplboolean 类型变量不加 is 前缀(POJO/数据库字段场景)
Service/Repository 层方法命名规约:
- 获取单个对象:
get前缀(getUserById) - 获取列表:
list前缀(listUsersByRole) - 获取统计:
count前缀(countActiveUsers) - 插入:
save/insert前缀(saveUser) - 删除:
remove/delete前缀(removeUserById) - 修改:
update前缀(updateUserRole)
- 获取单个对象:
枚举类名带 Enum 后缀(
UserStatusEnum),枚举成员全大写下划线(ACTIVE、DISABLED)
注释规范
- 所有类、接口必须有中文 Javadoc 注释,说明用途和职责
- 所有 public 方法必须有中文 Javadoc 注释,包含功能说明、
@param、@return、@throws - 复杂业务逻辑、核心算法必须添加中文行内注释说明意图
- TODO 注释格式:
// TODO: [作者] 具体待办事项描述 - 禁止无意义注释,注释必须与代码保持同步
- Mapper XML 中的自定义 SQL 必须添加中文注释说明查询用途
- 注释掉的代码应直接删除,版本管理由 Git 负责,禁止保留注释代码
接口与返回值规范
- 统一使用 RESTful 风格接口,API 版本化管理:
/api/v1/... - 使用自定义
Result<T>统一返回,包含 code、message、data,成功码固定为 0,失败使用5位分段编码(如 10001、20001),绝大部分接口返回 HTTP 200 - Controller 接口添加 Swagger/OpenAPI 注解:
@Tag、@Operation - 请求参数接收:
- JSON 请求体使用
@RequestBody - 查询参数使用
@RequestParam - 路径参数使用
@PathVariable
- JSON 请求体使用
- 参数校验必须完善:
@NotBlank、@NotNull、@Size、@Valid等
日志规范
- 使用
@Slf4j(Lombok)+ Logback,禁止使用System.out.println() - 禁止使用
e.printStackTrace(),必须通过日志框架记录异常 - 日志信息必须使用中文,参数化格式:
log.info("用户 {} 登录成功", userId) - 日志级别使用规范:
- ERROR:系统异常、不可恢复错误,必须附带完整异常栈
- WARN:潜在问题、业务异常、降级处理
- INFO:关键业务节点(用户登录、订单创建、支付完成等)
- DEBUG:调试信息,生产环境关闭
- 绝不记录敏感数据(密码、令牌、身份证号、银行卡号)
- 异常日志必须包含上下文信息:
log.error("创建订单失败,用户ID:{}", userId, e) - 生产环境禁止使用 DEBUG 级别日志输出
异常规范
- 自定义异常必须使用中文错误信息
- 建立异常层次结构:
BaseException -> NotFoundException, ValidationException, BusinessException - 异常类必须包含错误码(code)和中文错误信息(message)
- 业务校验失败抛出
BusinessException,资源不存在抛出NotFoundException - 使用
@RestControllerAdvice做全局异常捕获和处理 - 禁止在 Controller 层捕获异常后返回错误响应,统一由全局异常处理器处理
@Transactional必须指定rollbackFor = Exception.class- 捕获异常时禁止空 catch 块,至少记录日志
- 返回类型为基本数据类型时,return 包装数据类型对象可能自动拆箱产生 NPE,必须判空
- 集合元素即使
isNotEmpty,取出的数据元素也可能为 null,使用前必须判空
格式规范
- 统一使用 4 空格缩进,禁止 Tab
- 单行代码长度不超过 120 字符
- 方法体长度不超过 80 行,超过必须拆分
- 方法参数不超过 5 个,超过使用对象封装
- 大括号不换行(K&R 风格),一行一条语句
- if/else/for/while/do 语句必须使用大括号,即使只有一行代码
- 类成员排列顺序:静态变量 → 实例变量 → 构造方法 → 公有方法 → 私有方法
- 方法内部定义的局部变量应在首次使用时声明,不要集中在方法顶部
OOP 规约
- 所有覆写方法必须加
@Override注解 - 构造方法里禁止加入业务逻辑,如有初始化逻辑应放在
@PostConstruct或init方法中 - POJO 类必须写
toString方法,便于排查问题 - 可变参数必须放在参数列表最后
- 避免通过类的对象引用来访问静态变量或方法,应直接使用类名访问
equals方法必须满足对称性、传递性、一致性,重写equals必须同时重写hashCode- 优先使用基本数据类型而非包装类型,避免自动拆箱导致的 NPE
集合处理
ArrayList的subList结果不可强转成ArrayList,否则抛出ClassCastException- 使用
Map的entrySet遍历键值对,keySet仅遍历键 Map的 key 为自定义对象时必须重写hashCode和equals- foreach 循环里禁止进行元素的
remove/add操作,应使用Iterator Arrays.asList()返回的集合不能使用add/remove/clear方法- 集合转数组必须使用
toArray(T[] array),避免类型转换异常 - 泛型通配符:
<? extends T>上界通配符不能add,<? super T>下界通配符不能get
代码质量强制要求
- 禁止空指针:所有可能为 null 的返回值必须判空或使用
Optional,禁止信任外部输入 - 禁止魔法值:代码中不允许出现未解释的硬编码常量,必须定义为命名常量或枚举
- 禁止:
if (status == 1) - 正确:
if (status == UserStatusEnum.ACTIVE.getCode())
- 禁止:
- 禁止
==比较对象值,字符串使用equals(),对象使用Objects.equals() - 集合操作前必须判空,使用
CollectionUtils.isEmpty()或CollUtil.isEmpty() - 数值计算注意溢出和精度,金额必须使用
BigDecimal,禁止使用float/double - 日期时间使用
LocalDateTime,禁止使用java.util.Date - 禁止在循环中拼接字符串,使用
StringBuilder - 禁止在循环中执行数据库操作,使用批量方法
- switch 语句必须包含 default 分支
- 所有资源(流、连接)必须使用 try-with-resources 关闭
- 并发场景必须使用线程安全集合或加锁,禁止在多线程中使用非安全集合
- 优先使用 JDK8+ 特性:Stream、Lambda、Optional
- 方法入参必须校验:外部传入参数、跨层调用参数、public 方法入参
- 优先使用卫语句减少 if-else 嵌套,尽早 return
并发处理
- 线程资源必须通过线程池提供,禁止显式
new Thread() - 线程池禁止使用
Executors创建,必须通过ThreadPoolExecutor方式,明确线程池参数 - 必须回收自定义
ThreadLocal变量,使用try-finally调用remove(),避免内存泄漏 - 多线程加锁必须保持一致的加锁顺序,避免死锁
SimpleDateFormat线程不安全,使用DateTimeFormatter替代- 并发修改同一记录时必须加锁,避免数据不一致
volatile解决多线程内存可见性问题,但不能保证原子性,原子操作使用Atomic类
最佳实践
- 始终使用构造器注入(
@RequiredArgsConstructor) - 使用 DTO 进行请求/响应,绝不直接暴露实体类
- Service 层只依赖 Repository,禁止直接注入 Mapper
- Repository 层封装所有数据库操作,对 Service 层屏蔽 Mapper 细节
- 读操作使用
@Transactional(readOnly = true) - 使用 MapStruct 进行实体与 DTO 的映射
- 使用 Spring Security + JWT 实现认证授权
- 非阻塞操作使用
@Async - 启用 Actuator 端点进行应用监控