You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何访问Quarkus ClientWebApplicationException的内部数据?

解决Quarkus Rest Client异常中获取请求上下文的方案

要在异常映射器中拿到请求方法、URI、请求头以及客户端标识,核心思路是通过请求/响应拦截器捕获并传递请求上下文,替换默认抛出的ClientWebApplicationException为自定义异常,从而在异常映射器中直接获取所需信息。

步骤1:定义请求上下文封装类

先封装需要的请求信息,方便后续传递:

public class RequestContext {
    private String method;
    private URI uri;
    private Map<String, List<String>> headers;
    private String clientKey; // 区分不同RestClient的标识,对应@RegisterRestClient的configKey

    // 省略getter/setter
    public String getMethod() { return method; }
    public void setMethod(String method) { this.method = method; }
    public URI getUri() { return uri; }
    public void setUri(URI uri) { this.uri = uri; }
    public Map<String, List<String>> getHeaders() { return headers; }
    public void setHeaders(Map<String, List<String>> headers) { this.headers = headers; }
    public String getClientKey() { return clientKey; }
    public void setClientKey(String clientKey) { this.clientKey = clientKey; }
}

步骤2:实现请求拦截器捕获上下文

通过ClientRequestFilter在请求发送前捕获上下文,并存入请求属性中:

public class RequestContextFilter implements ClientRequestFilter {
    public static final String CONTEXT_KEY = "rest-client-request-context";

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        RequestContext context = new RequestContext();
        context.setMethod(requestContext.getMethod());
        context.setUri(requestContext.getUri());
        context.setHeaders(new HashMap<>(requestContext.getHeaders()));
        // 获取当前RestClient的configKey,用于区分不同客户端
        String clientKey = (String) requestContext.getProperty("org.eclipse.microprofile.rest.client.clientKey");
        context.setClientKey(clientKey);
        
        // 将上下文存入请求属性,后续响应拦截器可直接获取
        requestContext.setProperty(CONTEXT_KEY, context);
    }
}

步骤3:实现响应拦截器抛出自定义异常

拦截4xx/5xx响应,取出请求上下文,抛出自定义异常(继承ClientWebApplicationException,保留原有响应信息):

public class RestClientException extends ClientWebApplicationException {
    private final RequestContext requestContext;

    public RestClientException(Response response, RequestContext requestContext) {
        super(response);
        this.requestContext = requestContext;
    }

    public RequestContext getRequestContext() {
        return requestContext;
    }
}

对应的响应拦截器:

public class ErrorResponseFilter implements ClientResponseFilter {
    @Override
    public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
        if (responseContext.getStatus() >= 400) {
            // 取出之前存入的请求上下文
            RequestContext context = (RequestContext) requestContext.getProperty(RequestContextFilter.CONTEXT_KEY);
            // 构建响应对象并抛出自定义异常
            Response response = Response.fromResponse(responseContext.getResponse()).build();
            throw new RestClientException(response, context);
        }
    }
}

步骤4:注册拦截器到RestClient接口

在你的RestClient接口上绑定这两个拦截器:

@RegisterRestClient(configKey = "user-service-client")
@ClientRequestFilter(RequestContextFilter.class)
@ClientResponseFilter(ErrorResponseFilter.class)
public interface UserServiceClient {
    // 你的接口方法,例如:
    @GET
    @Path("/users/{id}")
    User getUserById(@PathParam("id") Long id);
}

步骤5:实现异常映射器处理自定义异常

在异常映射器中直接获取请求上下文和响应信息,按需构造错误响应:

@Provider
public class RestClientExceptionMapper implements ExceptionMapper<RestClientException> {
    @Override
    public Response toResponse(RestClientException exception) {
        RequestContext requestContext = exception.getRequestContext();
        Response originalResponse = exception.getResponse();

        // 提取所需信息:客户端标识、请求方法、URI、请求头等
        String clientId = requestContext.getClientKey();
        String requestMethod = requestContext.getMethod();
        URI requestUri = requestContext.getUri();
        Map<String, List<String>> requestHeaders = requestContext.getHeaders();

        // 构造自定义错误返回体(示例用简单实体类)
        ErrorDetails errorDetails = new ErrorDetails();
        errorDetails.setClientId(clientId);
        errorDetails.setRequestMethod(requestMethod);
        errorDetails.setRequestUri(requestUri.toString());
        errorDetails.setRequestHeaders(requestHeaders);
        errorDetails.setStatusCode(originalResponse.getStatus());
        errorDetails.setStatusReason(originalResponse.getStatusInfo().getReasonPhrase());

        return Response.status(originalResponse.getStatus())
                .entity(errorDetails)
                .type(MediaType.APPLICATION_JSON)
                .build();
    }

    // 错误详情实体类
    public static class ErrorDetails {
        private String clientId;
        private String requestMethod;
        private String requestUri;
        private Map<String, List<String>> requestHeaders;
        private int statusCode;
        private String statusReason;

        // 省略getter/setter
    }
}

关键注意事项

  1. 客户端标识:通过org.eclipse.microprofile.rest.client.clientKey属性获取的是@RegisterRestClient中配置的configKey,可直接用来区分不同的RestClient实例。
  2. 避免内存泄漏:使用请求属性存储上下文而非ThreadLocal,无需手动清理,请求结束后上下文会自动销毁。
  3. 兼容性:自定义异常继承ClientWebApplicationException,保证Quarkus RestClient的原有错误处理逻辑不受影响。

内容的提问来源于stack exchange,提问作者Anton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 06:50:19