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

Quarkus @ServerExceptionMapper:解包异常并转发至后续异常映射器

问题描述

我有若干会抛出WebApplicationException的HTTP端点,这些异常已通过全局异常映射器处理:

@ServerExceptionMapper
public Response unwrapWebApplicationException(WebApplicationException exception) {
    return Response
            .status(exception.getResponse().getStatus())
            .entity("Bla bla something happened, technical details: " + exception.getResponse().getEntity())
            .build();
}

但部分场景下,WebApplicationException会被ArcUndeclaredThrowableException等异常包裹,示例代码如下:

@Path("/hello")
public class GreetingResource {
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        WebApplicationException webApplicationException = new WebApplicationException(Response.Status.CONFLICT);
        throw new ArcUndeclaredThrowableException(webApplicationException);
    }
}

我的目标是编写一个异常映射器,解包ArcUndeclaredThrowableException后,将内部异常交给已存在的后续异常映射器处理。我尝试用jakarta.ws.rs.ext.Providers#getExceptionMapper实现逻辑,编写了工具方法:

/**
 * Attempts to unwrap the exception's cause (shallowly, not recursively) and invoke that exception's exception mapper instead.
 * If the exception has no cause, it is thrown as-is. If it has a cause but the cause has no exception mapper, the cause exception is thrown as-is.
 */
private static <T extends Throwable> Response unwrapCause(T exception, Providers providers) throws Throwable {
    var cause = exception.getCause();
    if (cause == null) {
        throw exception;
    }
    @SuppressWarnings("unchecked") Class<Throwable> exceptionClass = (Class<Throwable>) cause.getClass();
    ExceptionMapper<Throwable> exceptionMapper = providers.getExceptionMapper(exceptionClass);
    if (exceptionMapper == null) {
        throw cause;
    }
    return exceptionMapper.toResponse(cause);
}

但获取Providers实例时遇到问题:

  1. 将Providers作为异常映射器方法参数时,Quarkus构建报错:
Caused by: java.lang.RuntimeException: Parameter 'providers' of method 'unwrapArcUndeclaredThrowableExceptions of class 'org.acme.ExceptionMappers' is not allowed
    at org.jboss.resteasy.reactive.server.processor.generation.exceptionmappers.ServerExceptionMapperGenerator.getTargetMethodParamsInfo(ServerExceptionMapperGenerator.java:607)
    at org.jboss.resteasy.reactive.server.processor.generation.exceptionmappers.ServerExceptionMapperGenerator.generateRRResponse(ServerExceptionMapperGenerator.java:478)
    at org.jboss.resteasy.reactive.server.processor.generation.exceptionmappers.ServerExceptionMapperGenerator.generateGlobalMapper(ServerExceptionMapperGenerator.java:313)
    at io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveScanningProcessor.handleCustomAnnotatedMethods(ResteasyReactiveScanningProcessor.java:410)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:849)
    at io.quarkus.builder.BuildContext.run(BuildContext.java:256)
    at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
    at java.base/java.lang.Thread.run(Thread.java:1583)
    at org.jboss.threads.JBossThread.run(JBossThread.java:501)
  1. 使用@Context注入Providers时,运行时报错:
java.lang.IllegalStateException: This should never be called
    at org.acme.ExceptionMappers$GeneratedExceptionHandlerFor$WebApplicationException$OfMethod$unwrapWebApplicationException.toResponse(Unknown Source)
    at org.acme.ExceptionMappers$GeneratedExceptionHandlerFor$WebApplicationException$OfMethod$unwrapWebApplicationException.toResponse(Unknown Source)
    at org.acme.ExceptionMappers.unwrapCause(ExceptionMappers.java:49)
    at org.acme.ExceptionMappers.unwrapArcUndeclaredThrowableExceptions(ExceptionMappers.java:32)
    at org.acme.ExceptionMappers$GeneratedExceptionHandlerFor$ArcUndeclaredThrowableException$OfMethod$unwrapArcUndeclaredThrowableExceptions.toResponse(Unknown Source)
    at org.acme.ExceptionMappers$GeneratedExceptionHandlerFor$ArcUndeclaredThrowableException$OfMethod$unwrapArcUndeclaredThrowableExceptions.toResponse(Unknown Source)
    at org.jboss.resteasy.reactive.server.core.RuntimeExceptionMapper.mapException(RuntimeExceptionMapper.java:98)
    at org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext.mapExceptionIfPresent(ResteasyReactiveRequestContext.java:346)
    at org.jboss.resteasy.reactive.server.handlers.ExceptionHandler.handle(ExceptionHandler.java:15)
    at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:150)
    at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:147)
    at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:599)
    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:1583)
解决方案

在Quarkus的Resteasy Reactive环境中,直接操作Providers会触发兼容性问题,最可靠的方案是抛出解包后的内部异常,让框架自动匹配已有的异常映射器。

基础实现

针对ArcUndeclaredThrowableException编写专用映射器:

@ServerExceptionMapper
public Response unwrapArcUndeclaredThrowableException(ArcUndeclaredThrowableException exception) throws Throwable {
    // 直接抛出内部异常,框架会自动触发WebApplicationException的映射器处理
    throw exception.getCause();
}

原理说明

Resteasy Reactive的异常处理流程支持链式匹配:当当前映射器抛出新异常时,框架会重新启动异常匹配逻辑,自动找到最适合的映射器(也就是你已有的WebApplicationException全局映射器)。这种方式完全避开了Providers的注入和调用问题,逻辑简洁且符合框架设计。

扩展处理(可选)

如果需要处理多层嵌套的异常,或者要确保只传递有对应映射器的异常,可以实现递归解包逻辑:

@ServerExceptionMapper
public Response unwrapWrappedExceptions(Throwable exception) throws Throwable {
    Throwable current = exception;
    // 递归解包,直到找到WebApplicationException或无嵌套异常
    while (current.getCause() != null && !(current instanceof WebApplicationException)) {
        current = current.getCause();
    }
    // 若最终找到WebApplicationException则抛出,否则抛出原异常
    throw current instanceof WebApplicationException ? current : exception;
}

注意:这个通用映射器的优先级要低于特定异常的映射器(比如你的WebApplicationException映射器),避免提前拦截其他异常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 09:54:54