ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Feign Validation Jakarta:使用 Jakarta Bean Validation 实现 Feign 接口请求参数前置校验

Feign Validation Jakarta:使用 Jakarta Bean Validation 实现 Feign 接口请求参数前置校验 后端API设计【免费下载链接】feignFeign makes writing java http clients easier项目地址https://gitcode.com/gh_mirrors/fe/feign点击查看免费下载Feign 的feign-validation-jakarta模块基于 Jakarta Bean Validationjakarta.validation为 Feign 构建的接口提供请求发出之前的参数校验能力它自动校验方法 body 参数以及任何标注了Valid的方法参数一旦发现约束违规Constraint Violation就直接抛出jakarta.validation.ConstraintViolationExceptionHTTP 请求根本不会发送出去。读完本文你将掌握该模块的 Maven 引入方式、两种配置方法默认 Validator 工厂与显式 Validator 校验分组、底层MethodInterceptor的执行时机与校验逻辑实现以及如何用单元测试验证校验失败不发请求这一关键行为。一、模块定位把校验前移到 HTTP 请求之前很多 HTTP 客户端在请求发出后才由服务端校验参数或者依赖调用方自己手工校验容易遗漏且散落各处。feign-validation-jakarta提供的是声明式、集中式的前置校验对 Feign 接口方法的body 参数即 Contract 解析出的bodyIndex所指向的参数始终执行校验对方法上其他标注了Valid的参数例如携带复杂对象的 header、query 参数执行校验校验失败时抛出jakarta.validation.ConstraintViolationException且请求从未被派发。当前模块被标记为Experimental原因是它依赖的底层扩展点feign.interceptor.MethodInterceptorAPI 仍在演进中见 validation-jakarta/README.md 与 MethodInterceptor.java 中的Experimental标注。对于仍在使用传统javax.validation命名空间的场景请使用同仓库的兄弟模块feign-validation其用法与本文完全平行仅命名空间不同。二、引入依赖在pom.xml中加入以下依赖dependency groupIdio.github.openfeign/groupId artifactIdfeign-validation-jakarta/artifactId version${feign.version}/version /dependency从该模块自身的 validation-jakarta/pom.xml 可以看出几个关键事实模块坐标feign-validation-jakarta父 POM 为feign-parent当前仓库版本为 13.16-SNAPSHOT编译目标为Java 17main.java.version与maven.compiler.source/target均为 17核心依赖只有feign-core与jakarta.validation-api模块本身不捆绑具体 Validator 实现测试阶段使用hibernate-validatorJakarta 版本作为校验实现、expressly提供 EL 表达式支持、mockwebserver模拟 HTTP 服务端。因此在实际业务中你还需要在 classpath 中提供 Jakarta Bean Validation 的实现如 Hibernate Validator 6.2否则Validation.buildDefaultValidatorFactory()无法创建出可用的Validator。三、快速上手默认 Validator 工厂最小化配置只需一行.methodInterceptor(...)Api api Feign.builder() .methodInterceptor(BeanValidationMethodInterceptor.usingDefaultFactory()) .target(Api.class, https://example.com);usingDefaultFactory()是一个静态工厂方法其底层实现见 BeanValidationMethodInterceptor.java等价于new BeanValidationMethodInterceptor( Validation.buildDefaultValidatorFactory().getValidator());即使用ValidationAPI 构建默认的校验器工厂并取得Validator实例。四、进阶用法显式 Validator 与校验分组当默认工厂无法满足需求例如需要自定义MessageInterpolator、TraversableResolver、约束校验器工厂或使用校验分组时可以直接构造拦截器并传入分组类型Validator validator Validation.buildDefaultValidatorFactory().getValidator(); Feign.builder() .methodInterceptor(new BeanValidationMethodInterceptor(validator, Create.class)) .target(Api.class, https://example.com);构造函数签名见源码 L55-L58为public BeanValidationMethodInterceptor(Validator validator, Class?... groups)validator校验器实例完全由调用方掌控groups可变参数指定 Bean Validation 分组如Create.class、Update.class校验时仅执行这些分组下的约束groups为null时会被安全转换为空数组L57此时等价于默认分组校验。这样你就可以在同一个 DTO 上定义创建时必填、更新时可空等分组差异化约束并让 Feign 客户端按调用场景选择分组。五、底层原理MethodInterceptor 在请求流水线中的位置BeanValidationMethodInterceptor实现了 Feign 的feign.interceptor.MethodInterceptor接口见 MethodInterceptor.java。该接口是环绕式around-style拦截器文档明确说明其执行时机每次方法调用执行一次位于 Contract 将方法参数解析为RequestTemplate之后、RequestInterceptor修改模板之前。也就是说拦截器能拿到解析完成、尚未发送的RequestTemplateheaders、query params、body 字节Contract 解析出的MethodMetadata原始方法参数数组arguments。拦截器通过Chain链式委托继续执行下游请求拦截器 → HTTP → 响应拦截器 → 解码器并可通过Invocation.response()在链路完成后拿到Response。其核心约定MethodInterceptor.java短路直接返回某个值而不调用Chain.next(...)可完全跳过 HTTP 交换异常传播抛出的任何Throwable会在重试处理之后直接呈现在 Feign 接口方法的调用方andThen将多个拦截器组合成链apply把拦截器应用到现有链上。BeanValidationMethodInterceptor.intercept正是利用短路语义实现前置校验Override public Object intercept(Invocation invocation, Chain chain) throws Throwable { SetConstraintViolationObject violations collectViolations(invocation); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); } return chain.next(invocation); }一旦发现违规立即抛出ConstraintViolationException并阻断链的执行从而保证 HTTP 请求永远不会发出。这一定位在MethodInterceptor的 Javadoc 中被明确为仅靠RequestInterceptor/ResponseInterceptor不够、需要同时访问类型化参数对象与已解析请求时的扩展点。从调用链上看methodInterceptor注册入口位于 BaseBuilder.javaExperimental同步执行路径由SynchronousMethodHandler持有ListMethodInterceptor异步路径由AsynchronousMethodHandler持有见 SynchronousMethodHandler.java 与 AsynchronousMethodHandler.java。异步链路中多个拦截器通过reduce(MethodInterceptor::andThen)组合后apply(endOfChain)形成完整链AsynchronousMethodHandler.java。六、校验范围与跳过规则源码级解析collectViolations方法是整个模块的核心逻辑BeanValidationMethodInterceptor.java其处理流程如下private SetConstraintViolationObject collectViolations(Invocation invocation) { Object[] arguments invocation.arguments(); if (arguments null || arguments.length 0) { return Collections.emptySet(); // 无参方法直接通过 } SetConstraintViolationObject violations new LinkedHashSet(); Object body invocation.body(); if (body ! null) { violations.addAll(validator.validate(body, groups)); // ① body 始终校验 } Integer bodyIndex invocation.methodMetadata().bodyIndex(); Annotation[][] parameterAnnotations parameterAnnotationsCache.computeIfAbsent( invocation.method(), Method::getParameterAnnotations); // ② 注解缓存 for (int i 0; i arguments.length; i) { if (arguments[i] null) { continue; // ③ null 参数跳过 } if (bodyIndex ! null bodyIndex i) { continue; // ④ 跳过 body 本身避免重复校验 } if (hasValidAnnotation(parameterAnnotations[i])) { // ⑤ 仅校验 Valid 参数 violations.addAll(validator.validate(arguments[i], groups)); } } return violations; }逐条解读规则无参方法直通arguments为null或长度为 0 时返回空集合不产生任何校验开销body 参数总是校验通过Invocation.body()获取内部按methodMetadata.bodyIndex()定位非空即执行validator.validate(body, groups)。注意 body 为null时跳过校验——如果希望body 不能为空需要由 DTO 上的NotNull之类约束承担或自行在调用方处理注解缓存方法参数的注解数组通过ConcurrentHashMapMethod, Annotation[][]按反射Method缓存避免每次调用都重复反射获取注解兼顾了校验功能的并发安全与性能Valid 参数遍历所有参数凡标注jakarta.validation.Valid且非 body 位置、非 null 的一律执行validate。这意味着复杂对象形式的 header 参数、query 参数对象都可以声明式校验去重body 位置通过bodyIndex识别并跳过确保 body 只被校验一次。需要特别指出的是校验触发以参数上有Valid注解为前提BeanValidationMethodInterceptor不会对接口方法本身做方法级校验method validation这与在服务端使用Validated做方法参数校验的场景不同属于客户端出站请求的前置把关。七、行为验证测试用例是如何证明请求未发出的仓库测试 BeanValidationMethodInterceptorTest.java 用 MockWebServer 精确验证了上述语义测试模型定义如下static class Payload { NotNull public String name; NotBlank public String description; // ... } interface Api { RequestLine(POST /things) String create(Payload body); RequestLine(GET /things/{id}) String fetch(Param(id) String id, Valid HeaderInfo info); RequestLine(GET /things) String list(); }四个测试用例分别覆盖validBodyReachesServerbody 合法时请求正常发出server.getRequestCount() 1返回服务端响应invalidBodyThrowsBeforeRequest构造namenull、description的 Payload断言抛出ConstraintViolationException、getConstraintViolations()大小为 2同时命中NotNull与NotBlank两条约束且server.getRequestCount()为 0 ——没有任何请求到达服务端invalidNonBodyValidParameterThrowsBeforeRequest非 body 的Valid HeaderInfotenant为空白字符串违规时同样在请求前抛出异常证明Valid参数校验确实生效noArgsMethodPassesThrough无参方法list()正常通过并返回响应验证无参直通规则。这一组测试同时验证了校验失败不发请求与校验成功正常发请求两条路径是理解该模块行为的最佳参考。测试还展示了如何在测试环境快速搭建 Feign 客户端使用okhttp3.mockwebserver作为目标地址。八、注意事项与适用边界必须存在 Validator 实现模块只依赖jakarta.validation-api运行期需要如 Hibernate ValidatorJakarta 变体这样的实现使用Email、Size等需要 EL 的约束时还需提供 EL 表达式实现仓库测试即用expressly。ExperimentalAPIMethodInterceptor与拦截器本身均标注Experimental底层 API 稳定前可能发生变化生产环境引入前需评估升级成本。null 与空参语义null 参数一律跳过无参方法零开销直通body 为 null 时不做必填判断。异常类型校验失败抛出的jakarta.validation.ConstraintViolationException会在重试处理之后直接传播给 Feign 方法调用方可在客户端统一捕获并转为 4xx 或业务错误无需任何网络往返。与javax版本的选择JDK/Jakarta 体系下使用本模块jakarta.validation遗留的javax.validation命名空间请参考 validation/README.md 中的feign-validation模块两者 API 与行为一一对应。九、相关资源模块文档validation-jakarta/README.md模块构建配置validation-jakarta/pom.xml核心实现BeanValidationMethodInterceptor.java单元测试BeanValidationMethodInterceptorTest.java底层扩展点MethodInterceptor.java、Invocation.java拦截器注册与链路BaseBuilder.java、SynchronousMethodHandler.java、AsynchronousMethodHandler.java赞分享后端API设计【免费下载链接】feignFeign makes writing java http clients easier项目地址https://gitcode.com/gh_mirrors/fe/feign点击查看免费下载相关推荐Feign Validation在请求发出前用 Bean Validation 校验 Feign 接口参数的实战指南Feign Validation在请求发出前用 Bean Validation 校验 Feign 接口参数的实战指南 导读 本文围绕 Feign 官方子模块后端API设计3步在Mac上无缝运行Windows应用Whisky终极兼容方案3步在Mac上无缝运行Windows应用Whisky终极兼容方案 想在苹果电脑上运行Windows专属软件却不想安装虚拟机Whisky为你提供了革命性的解决后端API设计上一篇如何高效使用开源工具MOOTDX通达信数据接口深度实战指南下一篇a-picture-is-worth-a-1000-words项目可访问性评估工具自动化与手动测试创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表