开发接口的时候,总有人争论:参数校验到底该放在哪一层?Controller?Service?还是 DTO 里?
常见的三层结构
大多数项目都分 Controller、Service、DAO 三层。用户请求进来,先到 Controller,再调 Service 处理业务,最后访问数据库。那参数校验放哪儿合适?
放在 Controller 层最合理
Controller 是入口,就像小区的门卫。快递员来了,门卫先看是不是合法访客,带不带证件,包裹有没有贴单。不符合条件直接拦下,不用让住户出来处理。
同理,前端传了个空字符串或者格式错误的邮箱过来,Controller 应该第一时间发现并返回 400 错误,别把脏数据往里面传。Spring Boot 用 @Valid 注解就能轻松实现:
@PostMapping("/users")
public ResponseEntity<String> createUser(@Valid @RequestBody UserRequest request) {
userService.create(request);
return ResponseEntity.ok("创建成功");
}
配合 @NotBlank、@Email 这些注解,基本的合法性检查就有了。
Service 层要不要再校验?
如果你觉得“万一对方绕过 Controller 直接调 Service 呢”,那确实得防一手。但这种情况属于内部调用,责任分明。比如你同事写了个定时任务调你的方法,传了个 null 进来,这时候报错让他自己改就行。
Service 更关注业务规则,比如“用户年龄必须大于18才能注册会员”,这种不是格式问题,而是逻辑约束。这类判断留在 Service 没毛病。
if (user.getAge() < 18) {
throw new BusinessException("未成年人不能开通会员");
}
DTO 自带校验注解
很多人喜欢在 DTO(数据传输对象)上加 @NotNull、@Min 这些注解,然后在 Controller 里用 @Valid 触发。这其实是推荐做法,把校验规则定义在数据结构上,清晰又复用。
比如同一个 UserRequest,在新增和修改时可能校验规则不同,可以用分组来区分:
public class UserRequest {
@Null(groups = Create.class)
@NotNull(groups = Update.class)
private Long id;
@NotBlank
private String name;
}
别在 DAO 层做参数校验
DAO 只管和数据库打交道。你让它去判断字符串长度是不是超了,等于让收银员去检查顾客身份证真伪——职责错乱。而且等走到 DAO,说明前面层层失守,系统已经处于异常状态了。
小项目可以灵活点
两三个接口的小工具,非要把校验拆来拆去反而累赘。直接在 Controller 里 if 判断也无妨。重点是别让错误数据流入核心流程。
就像煮面,水没开就下面条,结果只能是一锅糊。参数校验就是烧开水这一步,别省。