我有一些 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)
如何编写一个仅解包某个异常的异常映射器,让原因通过任何其他可能定义的异常映射器?
首先,我很抱歉让这件事变得比应有的更加困难(请打开一个问题要求我们让它变得更容易)。
有一种方法可以满足您的需要,但它涉及使用一堆内部类型(不属于公共 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);
}
}