Service层手动校验是补充而非替代声明式校验,适用于依赖数据库查询、多字段联动、调用外部服务及业务状态相关等无法用@Valid表达的场景。

Spring Boot 中 Service 层手动校验,通常出现在两种情况:一是绕过自动校验(比如参数未用 @Valid 标注),二是需要执行无法用注解表达的动态或上下文相关逻辑(如“用户名不能与当前登录用户相同”“库存扣减前需查实时余量”)。它不是替代声明式校验,而是补充。
什么时候必须手动校验
以下场景不适合仅靠 @Valid 或 @Validated:
- 校验依赖数据库查询结果(例如:检查手机号是否已被其他用户注册)
- 校验涉及多个字段联动(例如:“开始时间不能晚于结束时间”,且两个时间来自不同对象)
- 校验需调用外部服务(例如:验证身份证号是否通过公安接口核验)
- 校验逻辑随业务状态变化(例如:只有在“审核中”状态下才允许修改备注)
常用手动校验方式
推荐使用 Spring 内置的 Validator 接口 + 自定义校验逻辑,而非裸写 if 判断,以保持可测试性和一致性:
-
注入 Validator 实例:Spring Boot 自动配置了
LocalValidatorFactoryBean,可直接@Autowired使用 -
调用 validate() 方法:传入待校验对象,返回
Set<constraintviolation>></constraintviolation>;若非空,说明有校验失败项 -
结合分组校验(Groups):对同一对象在不同业务路径下启用不同规则集,例如
RegisterCheck.class和UpdateCheck.class
示例代码:
@Service
public class UserServiceImpl implements UserService {
<pre class="brush:php;toolbar:false;">@Autowired
private Validator validator;
@Override
public void updateUser(User user) {
// 1. 先触发基础注解校验(如 @NotBlank, @Email)
Set<constraintviolation>> violations = validator.validate(user);
if (!violations.isEmpty()) {
String errorMsg = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "));
throw new IllegalArgumentException("参数校验失败: " + errorMsg);
}
// 2. 手动补充业务校验
if (user.getAge() != null && user.getAge() <p>}</p></constraintviolation>避免重复校验和耦合
手动校验容易写成“散装 if”,带来维护负担。建议:
- 将校验逻辑封装为独立方法(如
checkUsernameUniqueness()),便于单元测试和复用 - 不与 DAO 层强耦合——校验方法只负责判断,不执行 save / update;由调用方统一处理事务边界
- 错误提示尽量复用统一异常体系(如自定义
BizException并携带错误码),不要混用IllegalArgumentException和业务异常
与声明式校验协同工作
Service 层可以同时启用两种方式:
- 用
@Validated在类/方法上开启 AOP 校验,拦截基础规则 - 在方法体内用
validator.validate()补充动态规则 - 二者不冲突,且顺序可控:AOP 校验先发生,手动校验后执行,适合做“二次把关”











