OpenAPI与Spring Boot3:契约优先模式下为特定端点用DeferredResult的方法
契约优先模式下特定端点使用DeferredResult的可行方案
方案一:OpenAPI扩展标记 + 拦截器局部处理
- 在你的OpenAPI契约中,给需要异步处理的特定端点添加自定义扩展字段,比如
x-use-deferred-result: true - 实现一个Spring MVC
HandlerInterceptor,在请求预处理阶段,根据当前请求的URI匹配契约中的端点元数据,检查是否携带该扩展标记 - 如果标记为true,就将同步处理逻辑转为
DeferredResult异步模式,避免全局包装所有端点
示例代码:
public class DeferredResultInterceptor implements HandlerInterceptor { // 假设已将OpenAPI契约的端点元数据加载到本地缓存 private final Map<String, Boolean> asyncEndpointCache = loadAsyncEndpointsFromContract(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestUri = request.getRequestURI(); if (Boolean.TRUE.equals(asyncEndpointCache.get(requestUri))) { DeferredResult<Object> deferredResult = new DeferredResult<>(); // 提交业务逻辑到异步线程池执行 CompletableFuture.runAsync(() -> { Object businessResult = executeAsyncBusinessLogic(request); deferredResult.setResult(businessResult); }); // 将DeferredResult绑定到请求上下文,替换原同步返回值 request.setAttribute("asyncResult", deferredResult); } return true; } }
方案二:自定义注解 + 返回值处理器
- 保留现有代码生成流程,在生成后的Controller方法中,给需要异步的方法手动添加自定义注解(比如
@UseDeferredResult) - 实现
HandlerMethodReturnValueHandler,在处理返回值时检查方法是否带有该注解,若存在则自动将返回值包装为DeferredResult
示例代码:
// 自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseDeferredResult {} // 生成后添加注解的Controller方法 @RestController public class GeneratedOrderController { @UseDeferredResult @GetMapping("/api/orders/{id}") public OrderDetail getOrderDetail(@PathVariable String id) { return orderService.getDetail(id); } } // 自定义返回值处理器 public class DeferredResultHandler implements HandlerMethodReturnValueHandler { @Override public boolean supportsReturnType(MethodParameter returnType) { return returnType.hasMethodAnnotation(UseDeferredResult.class); } @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) { DeferredResult<Object> deferredResult = new DeferredResult<>(); // 异步包装返回值 CompletableFuture.supplyAsync(() -> returnValue) .thenAccept(deferredResult::setResult) .exceptionally(ex -> { deferredResult.setErrorResult(ex); return null; }); mavContainer.setRequestHandled(true); webRequest.getNativeResponse(HttpServletResponse.class) .setAttribute(DeferredResult.class.getName(), deferredResult); } }
方案三:动态代理层局部包装
- 基于代码生成的Controller接口,创建动态代理实例,在代理逻辑中对指定端点的返回值进行
DeferredResult包装 - 将代理实例注册到Spring容器,替代原生成的Controller Bean,全程无需修改生成代码或契约模板
核心逻辑示例:
public class DeferredResultProxyCreator { public static <T> T createAsyncProxy(T targetController, Set<String> asyncEndpointPaths) { return (T) Proxy.newProxyInstance( targetController.getClass().getClassLoader(), targetController.getClass().getInterfaces(), (proxy, method, args) -> { // 从方法注解中解析对应端点路径 String endpoint = resolveEndpointFromMethodAnnotations(method); if (asyncEndpointPaths.contains(endpoint)) { DeferredResult<Object> deferredResult = new DeferredResult<>(); // 异步执行原方法逻辑 CompletableFuture.supplyAsync(() -> { try { return method.invoke(targetController, args); } catch (Exception e) { deferredResult.setErrorResult(e); return null; } }).thenAccept(deferredResult::setResult); return deferredResult; } // 非异步端点直接返回原结果 return method.invoke(targetController, args); }); } }
以上三种方案都无需修改mustache模板或打破现有工作流程,通过局部扩展机制实现特定端点的DeferredResult包装需求。
内容的提问来源于stack exchange,提问作者Sayros
相关产品推荐
相关产品推荐

