type
status
date
slug
summary
tags
category
icon
password
catalog
sort
1. 目的与范围
1.1 目的
- 统一团队对代码元素空状态(参数、返回值、字段等)的声明方式,结合编译期与运行期校验消除NPE
- 规范主流框架注解(Jakarta Validation、MyBatis、Spring Web)的使用方式,提升代码一致性
- 明确各层代码契约(如接口参数校验规则、DAO层返回值约定),减少协作沟通成本
- 通过注解强化代码可读性与可维护性,降低后期迭代风险
1.2 适用范围
- 所有基于Java开发的项目(JDK 11+),涵盖微服务、单体应用等架构
- 涉及框架:Spring Boot 2.7+(含Spring Web)、MyBatis 3.5+、Jakarta Validation 3.0+
- 所有开发人员、代码评审人员、测试人员及架构设计人员
- 强制要求:新增代码100%遵守;过渡要求:存量代码6个月内完成改造(核心模块优先)
1.3 参考标准
- JetBrains Annotations 官方文档:https://github.com/JetBrains/java-annotations
- Jakarta Validation 规范:https://jakarta.ee/specifications/bean-validation/3.0/
- MyBatis 官方文档:https://mybatis.org/mybatis-3/annotations.html
- 《Effective Java》第3版:第54条(返回空集合而非null)、第63条(慎用注解)
2. 基础注解与框架注解概述
2.1 核心注解分类
注解类型 | 作用域 | 核心框架/库 | 核心价值 |
编译期空安全注解 | 参数、返回值、字段 | JetBrains Annotations | 编译期识别NPE风险(如 @NotNull /@Nullable ) |
运行期校验注解 | DTO字段、接口参数 | Jakarta Validation | 运行期校验参数合法性(如 @NotBlank /@Email ) |
数据访问注解 | DAO方法、参数、结果映射 | MyBatis | 规范SQL映射与参数传递(如 @Param /@Result ) |
Web请求注解 | 控制器方法、参数 | Spring Web | 定义HTTP请求映射与参数绑定(如 @GetMapping /@RequestBody ) |
2.2 核心注解冲突说明
- JetBrains
@NotNull
vs Jakarta@NotNull
:前者是编译期提示(无运行时逻辑),后者是运行期校验(触发ConstraintViolationException
),需按分层场景区分使用(见第5章分层规范)。
- 框架注解优先级:同一元素若需多框架注解,按“功能分层”叠加(如
@PathVariable
+@NotBlank
,前者绑定请求参数,后者校验非空)。
3. JetBrains Annotations 规范(编译期空安全)
3.1 基础空安全注解
3.1.1 @NotNull
- 定义:标记元素(参数/返回值/字段)不允许为null,IDE编译期校验。
- 适用场景:
- 业务核心参数(如用户ID、订单号):
public void pay(@NotNull String orderId)
- 必须初始化的字段(如领域模型主键):
@NotNull private String id;
- 必然有返回值的方法(如查询已确认存在的数据):
@NotNull User getAdminUser()
- 强制要求:
- 被标记的参数需在方法内补充运行期校验(推荐
Objects.requireNonNull()
),示例: - 禁止对
@NotNull
元素做冗余null判断(如if (param == null)
,IDE会提示警告)。
3.1.2 @Nullable
- 定义:标记元素允许为null,提示调用方必须处理null场景,IDE编译期校验。
- 适用场景:
- 查询操作返回值(数据可能不存在):
@Nullable User findByPhone(String phone)
- 可选参数(如分页查询的“排序字段”):
List<User> list(@Nullable String sortField)
- 延迟加载的字段(如
User.extraInfo
可能未初始化):@Nullable String extraInfo;
- 强制要求:
- 调用
@Nullable
方法后必须显式判空,支持两种方式: - 禁止直接调用
@Nullable
对象的方法(如nullableUser.getName()
必须先判空)。
3.1.3 包级空安全注解
注解 | 作用域 | 语义 | 适用场景 |
@NotNullApi | package-info.java | 包内未显式标注的元素默认不可为null | 服务层(Service)、领域模型(Domain) |
@NullableApi | package-info.java | 包内未显式标注的元素默认可为null | 数据访问层(DAO)、外部接口适配层 |
- 使用方式:在包根目录创建
package-info.java
,示例:
3.2 方法契约与边界注解
3.2.1 @Contract
- 定义:描述方法“参数→返回值”的逻辑关系,辅助IDE精准分析代码流。
- 适用场景:工具类方法、有明确输入输出映射的函数(如
StringUtils
)。
- 语法规则:
value
:分号分隔的“条件→结果”表达式(如"null->false;!null->true"
)pure = true
:标记纯函数(无副作用,输入相同则输出相同)
- 示例:
3.2.2 测试边界注解
注解 | 作用 | 适用场景 | 约束要求 |
@TestOnly | 标记元素仅允许在测试代码中使用 | 测试Mock方法(如 mockOrder() ) | 生产代码调用必须在CR中驳回 |
@VisibleForTesting | 标记本应私有但为测试改为非私有的方法 | 需要单测覆盖的内部逻辑(如校验方法) | 访问修饰符需最小化(如 private→protected ) |
- 示例:
3.3 集合与类型注解
3.3.1 集合元素空状态
- 必须明确集合本身及元素的空状态,使用嵌套注解:
3.3.2 不可修改集合
- 返回不可修改的集合时,必须添加
@Unmodifiable
:
4. Jakarta Validation 注解规范(运行期校验)
4.1 核心校验注解
注解 | 作用 | 适用场景 | 与JetBrains注解的区别 |
@NotNull | 运行期校验元素非null(允许空字符串"") | 数字类型字段(如 Integer age ) | JetBrains @NotNull 是编译期提示 |
@NotBlank | 运行期校验字符串非null且非空白(去空格后长度>0) | 用户名、密码等字符串字段 | - |
@NotEmpty | 运行期校验集合非null且元素数量>0 | 订单明细列表( List<OrderItem> ) | - |
@Email | 运行期校验字符串符合邮箱格式 | 用户邮箱字段 | 可配合 regexp 自定义格式 |
@Pattern(regexp) | 运行期校验字符串匹配正则表达式 | 手机号( regexp = "^1[3-9]\\\\d{9}$" ) | - |
@Min /@Max | 运行期校验数字在指定范围内 | 年龄( @Min(18) ) | - |
4.2 使用规范
4.2.1 适用层级
- 仅用于DTO层(如
UserDTO
、OrderQuery
),作为外部输入的第一道防线(如HTTP请求参数、消息队列消息体)。
- 禁止在领域模型(
User
)、服务层方法参数直接使用(领域模型应通过业务逻辑校验,而非注解)。
4.2.2 与Spring Web结合
- 通过
@Valid
或@Validated
触发校验,示例:
4.2.3 自定义校验注解
- 复杂业务规则需定义自定义注解(如“密码强度校验”),示例:
5. MyBatis 注解规范(数据访问层)
5.1 核心注解分类
注解类型 | 核心注解 | 作用 |
SQL映射注解 | @Select /@Insert 等 | 定义SQL语句(替代XML映射) |
参数绑定注解 | @Param | 声明DAO方法参数名,便于SQL中引用 |
结果映射注解 | @Result /@Results | 定义查询结果与实体字段的映射关系 |
动态SQL注解 | @If /@Where 等 | 构建动态SQL(需配合MyBatis-3.5+) |
5.2 使用规范
5.2.1 参数绑定(@Param
)
- 方法参数≥2时必须用
@Param
指定名称,且名称需与SQL中的#{name}
一致:
- 禁止使用
@Param
传递复合对象(如@Param("user") User user
),建议直接用对象属性(如#{user.id}
)。
5.2.2 结果映射(@Result
/@Results
)
- 字段名与列名不一致时,必须用
@Result
显式映射,且需指定property
(实体字段)和column
(表列名):
- 复用性高的映射关系需定义为
@ResultMap
,避免重复代码:
5.2.3 与JetBrains注解结合
- DAO方法返回值:查询操作(
select
)用@Nullable
(数据可能不存在),新增/更新/删除用@NotNull
(返回影响行数或主键):
6. Spring Web 注解规范
6.1 核心注解分类
注解类型 | 核心注解 | 作用 |
请求映射注解 | @RequestMapping /@GetMapping 等 | 定义HTTP请求路径与方法 |
参数绑定注解 | @PathVariable /@RequestParam | 绑定URL路径参数/请求参数 |
请求体注解 | @RequestBody | 绑定HTTP请求体(JSON/XML) |
响应注解 | @ResponseBody /@ResponseStatus | 声明响应体格式/HTTP状态码 |
6.2 使用规范
6.2.1 请求映射注解
- 优先使用HTTP方法级注解(
@GetMapping
/@PostMapping
),避免通用@RequestMapping
:
- 路径命名规范:使用小写字母+连字符(),如
/user-orders
而非/userOrders
。
6.2.2 参数绑定与校验
@PathVariable
:路径参数必须用@NotNull
(Jakarta)校验非空,且名称需与URL占位符一致:
@RequestParam
:查询参数需指定required
属性(默认true
),可选参数需显式设为required = false
:
@RequestBody
:配合@Valid
触发Jakarta Validation校验,且DTO需用@Nullable
标记可能为null的字段:
6.2.3 响应注解
- 控制器方法返回值优先使用
ResponseEntity<T>
,明确HTTP状态码与响应体:
- 禁止使用
@ResponseBody
标注类(如@RestController
已包含该功能),避免冗余。
7. 分层注解规范
7.1 控制层(Controller)
层职责 | 核心注解组合 | 示例代码 |
请求接收与响应 | Spring Web注解 + Jakarta Validation注解 | java @GetMapping("/orders/{orderId}") public ResponseEntity<OrderVO> getOrder( @PathVariable("orderId") @NotNull String orderId, // 路径参数+非空校验 @RequestParam(required = false) @Nullable String viewType // 可选查询参数 ) { ... } |
请求体校验 | @RequestBody + @Valid + DTO(Jakarta注解) | java @PostMapping("/orders") public ResponseEntity<OrderVO> createOrder( @Valid @RequestBody @NotNull OrderDTO dto // 请求体+校验 ) { ... } |
7.2 服务层(Service)
层职责 | 核心注解组合 | 示例代码 |
业务逻辑处理 | JetBrains注解(编译期空安全) | java @Service public class OrderService { // 订单ID非空,返回值非空(业务约定) @NotNull public Order getOrder(@NotNull String orderId) { Objects.requireNonNull(orderId, "订单ID不能为空"); // 运行期兜底 return orderDao.selectById(orderId); } } |
方法契约声明 | @Contract (工具类方法) | java public class OrderUtils { @Contract("null -> false") public static boolean isPaid(Order order) { return order != null && order.getStatus() == 2; } } |
7.3 数据访问层(DAO)
层职责 | 核心注解组合 | 示例代码 |
数据查询 | MyBatis注解 + JetBrains @Nullable | java public interface OrderDao { @Select("SELECT * FROM t_order WHERE id = #{id}") @Nullable Order selectById(@NotNull String id); // 查询可能无结果 } |
数据写入 | MyBatis注解 + JetBrains @NotNull | java public interface OrderDao { @Insert("INSERT INTO t_order(...) VALUES(...)") @NotNull String insert(@NotNull Order order); // 写入必返回主键 } |
7.4 DTO层(数据传输对象)
层职责 | 核心注解组合 | 示例代码 |
输入参数校验 | Jakarta Validation注解(运行期校验) | java public class UserDTO { @NotBlank(message = "用户名不能为空") private String username; @Email(message = "邮箱格式错误") private String email; @Min(value = 18, message = "年龄不能小于18") private Integer age; } |
空状态声明 | JetBrains @Nullable (可选字段) | java public class UserDTO { @NotBlank private String username; @Nullable // 可选字段 private String avatar; } |
8. 代码评审检查清单
检查维度 | 检查项 | 权重 |
空安全注解 | 1. 所有public方法参数/返回值均有 @NotNull /@Nullable (JetBrains)<br>2. @Nullable 返回值调用处已判空 | 高 |
Jakarta Validation | 1. DTO字段已用 @NotBlank /@Email 等标注校验规则<br>2. 控制器方法@RequestBody 已加@Valid | 高 |
MyBatis注解 | 1. 多参数方法已用 @Param 命名<br>2. DAO查询方法返回值已加@Nullable | 中 |
Spring Web注解 | 1. 请求路径用小写+连字符,HTTP方法注解明确<br>2. @PathVariable 与URL占位符名称一致 | 中 |
注解冲突处理 | 1. 未混淆JetBrains @NotNull 与Jakarta @NotNull <br>2. 同一元素多注解功能不重叠 | 中 |
集合与工具类 | 1. 集合元素空状态已用嵌套注解标注(如 List<@Nullable String> )<br>2. 工具类方法已加@Contract | 低 |
9. 自动化校验与工具支持
9.1 IDE配置
- JetBrains注解检查:IDEA开启
Settings > Editor > Inspections > Java > Probable bugs > Nullability problems
(全选)。
- 框架注解提示:安装
Spring Boot
、MyBatis
插件,启用注解自动补全(Ctrl+Space
)。
9.2 CI/CD集成
- Jakarta Validation校验:通过
spring-boot-starter-validation
依赖,在测试阶段执行@Valid
校验用例。
- MyBatis注解检查:使用
mybatis-spring-boot-starter-test
,通过@MybatisTest
验证DAO注解与SQL匹配性。
- 静态扫描:SonarQube启用规则:
- S2259:避免对
@Nullable
值进行非null比较 - S3655:避免对
@NotNull
参数进行null检查 - 自定义规则:检查
@Param
命名与SQL参数一致性
9.3 冲突处理工具
- 注解冲突检测:使用
maven-enforcer-plugin
禁止在领域模型同时使用Jakarta@NotNull
与JetBrains@NotNull
。
- 冗余注解清理:IDEA插件
Annotation Cleaner
自动移除无效注解(如@ResponseBody
在@RestController
类中)。
附录:注解速查表(按场景)
场景 | 推荐注解组合 |
DTO非空字符串字段 | @NotBlank (Jakarta) |
控制器路径参数 | @PathVariable + @NotNull (Jakarta) |
DAO查询方法返回值 | MyBatis @Select + JetBrains @Nullable |
服务层核心参数 | JetBrains @NotNull + Objects.requireNonNull |
工具类纯函数 | JetBrains @Contract(pure = true) |
- 作者:Honesty
- 链接:https://blog.hehouhui.cn/archives/2400c7d0-9e17-80b9-a49b-f338380bbbbf
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章