Quarkus `@ServerExceptionMapper`:解包并转发到下一个异常映射器

问题描述 投票:0回答:1

我有一些 HTTP 端点最终会抛出

WebApplicationException
。这些使用全局异常映射器进行映射:

@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(Class<T> type)
来完成。我编写了以下实用程序:

/**
 * 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);
}

现在理论上我需要做的就是声明我的异常映射器并在其中调用

unwrapCause()
。但是,我无法获得
Providers
的正确实例。 Quarkus 关于 REST 的文档指出:

您的异常映射器可以声明以下任何参数类型:[...]任何 Context 对象

在这些上下文对象中,列出了

Providers
。然而,当我尝试这个时:

/**
 * If quarkus encounters a checked exception in some proxy code that bubbles up to some other code that does not declare that checked exception,
 * it needs to wrap the exception in a runtime exception for it to be valid Java. Quarkus uses the {@link ArcUndeclaredThrowableException} for this.
 * It is of no informational value, so let's just always unwrap it.
 */
@ServerExceptionMapper
public Response unwrapArcUndeclaredThrowableExceptions(ArcUndeclaredThrowableException exception, Providers providers) throws Throwable {
    return unwrapCause(exception, 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)

如果我尝试像这样在外部注入一个

Providers
实例:

@Context
Providers providers;

/**
 * If quarkus encounters a checked exception in some proxy code that bubbles up to some other code that does not declare that checked exception,
 * it needs to wrap the exception in a runtime exception for it to be valid Java. Quarkus uses the {@link ArcUndeclaredThrowableException} for this.
 * It is of no informational value, so let's just always unwrap it.
 */
@ServerExceptionMapper
public Response unwrapArcUndeclaredThrowableExceptions(ArcUndeclaredThrowableException exception) throws Throwable {
    return unwrapCause(exception, 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)

如何编写一个仅解包某个异常的异常映射器,让原因通过任何其他可能定义的异常映射器?

java quarkus resteasy
1个回答
0
投票

首先,我很抱歉让这件事变得比应有的更加困难(请打开一个问题要求我们让它变得更容易)。

有一种方法可以满足您的需要,但它涉及使用一堆内部类型(不属于公共 API 的一部分):

import io.quarkus.arc.ArcUndeclaredThrowableException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Providers;
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveContainerRequestContext;
import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveExceptionMapper;
import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;

public class Mappers {

    @Context
    Providers providers;

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

    @ServerExceptionMapper
    public Response unwrapArcUndeclaredThrowableExceptions(ArcUndeclaredThrowableException exception, ResteasyReactiveContainerRequestContext context) throws Throwable {
        return unwrapCause(exception, providers, context.getServerRequestContext());
    }

    private static <T extends Throwable> Response unwrapCause(T exception, Providers providers,
                                                              ServerRequestContext context) throws Throwable {
        var cause = exception.getCause();
        if (cause == null) {
            throw exception;
        }
        @SuppressWarnings("unchecked") Class<Throwable> exceptionClass = (Class<Throwable>) cause.getClass();
        ResteasyReactiveExceptionMapper<Throwable> exceptionMapper =
                (ResteasyReactiveExceptionMapper<Throwable>) providers.getExceptionMapper(exceptionClass);
        if (exceptionMapper == null) {
            throw cause;
        }
        return exceptionMapper.toResponse(cause, context);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.