同一种校验异常,可能在方法体执行前发生,也可能在方法体已经执行后发生。要判断一次工具调用是否产生了作用,需要检查违规位置和业务调用次数。
前置:HTTP 与工具的双入口校验。范围限普通同步 Spring 服务方法;下面片段与实验设计未运行。
参数校验 方法执行 返回值校验
Spring服务层方法校验通过AOP代理(Aspect-Oriented Programming,面向切面的代理拦截)执行,目标类需以@Validated启用并配置校验处理器及提供者。固定版本invoke方法在普通同步参数校验通过后调用invocation.proceed(),随后校验返回值,再向调用者返回。默认约束失败可抛ConstraintViolationException;启用适配时可改抛MethodValidationException。同一异常类型既可能发生在业务执行前,也可能发生在业务执行后。
返回值违反约束说明本次交付不满足约定,不等于方法体从未运行。不能把所有校验异常都当作用户参数错误或安全重试依据;已经产生的计数、外部请求等也不会因返回值校验自动撤销。是否回滚数据库写入须另验事务与校验拦截器的实际顺序及事务边界,本节不声称必定提交或回滚。此结论与既有错误分类衔接,新增的是执行次数证据,而非重复校验注解清单。
最小观测夹具
@Validated
public class MerchantProbe {
private final AtomicInteger entered = new AtomicInteger();
@NotNull
public String lookup(@NotBlank String merchantId) {
entered.incrementAndGet(); // 仅作内存观测,不调用真实商户服务
return "MISSING_RESULT".equals(merchantId) ? null : merchantId;
}
public int enteredCount() { return entered.get(); }
}
使用jakarta.validation.constraints.NotNull、NotBlank,java.util.concurrent.atomic.AtomicInteger及Spring的Validated;通过容器获取已配置方法校验的代理实例。片段省略容器和提供者配置,不是直接new即可生效的完整程序。
练习一 区分三个调用结果
每个用例使用独立夹具。经代理调用lookup(""),断言抛校验异常且enteredCount为0;调用lookup("MISSING_RESULT"),断言返回值约束失败且计数为1;正常ID返回原值且计数为1。记录参数/返回值违规位置,不只匹配异常类名。验收目标是区分“执行前拒绝”和“执行后拒绝交付”,不得为通过测试删除@NotNull。
练习二 核对 HTTP 与工具适配器
给HTTP和工具适配器接同一代理夹具,固定假模型仅请求一次合法但返回空结果的查询。断言底层计数恰为1、对外不泄露堆栈、不会把故障改写成用户输入错误或触发自动重放;用空ID作为执行前拒绝对照。若框架包装异常,追踪根因及违规位置,同时独立核对调用计数。这是建议的应用错误策略,不是Spring自动保证。
适用边界
本节没有验证响应式参数、流式结果、构造器或绕过代理的调用,也未实测项目异常包装链。数据库回滚取决于事务边界与拦截器顺序;返回值校验本身不会撤销外部请求或内存计数。用项目已有依赖运行验收,不要求为本例升级。
来源与版本
Spring Framework 7.0.10。资料原核查时间为 2026 年 10 月 27 日 18:10–18:11 JST,独立发布日期未标明。来源:Spring-driven Method Validation、MethodValidationInterceptor API、固定标签源码。