7

我需要一些帮助来了解 Websphere Liberty (18.0.0.1) 如何处理在 JAX-RS 端点调用中引发的异常。我正在使用 Liberty 功能jaxrs-2.0,因此应该由 WLP 提供实现。

现在,我的应用程序有一个接受 JSON 有效负载的 POST HTTP 端点,我想为所有可能的错误客户端输入提供自定义错误消息。

这是一个以我期望的方式起作用的案例:

  1. 客户端发送application/xml而不是application/json
  2. ClientErrorException容器抛出了一个
  3. 我可以使用我自己的异常映射器(实现ExceptionMapper<WebApplicationException>来处理这个异常(实际上是处理所有的 web 应用程序异常,我很好)
  4. 这样我可以格式化错误消息,用 ID 标记错误,无论需要什么。那挺好的

这是对我不起作用的情况:

  1. 客户端发送application/json,但正文为空
  2. 在这种情况下,核心例外是java.io.EOFException: No content to map to Object due to end of input- 是的,这看起来很准确
  3. 现在我无法弄清楚 - WLP 不是将其包装EOFException成某种WebApplicationException(我可以轻松处理),而是将异常问题包装成JaxRsRuntimeException

这里有几点:

  • 我不想创建一个映射器实现ExceptionMapper<JaxRsRuntimeException>,因为该异常不是 JAX-RS 2.0 规范的一部分,我必须提供对 JaxRsRuntimeException 的导入并将应用程序与一些特定于 Liberty 的库连接。
  • 一个可能的解决方案是让我的映射器实现一个通用ExceptionMapper<RuntimeException>和字符串检查,如果它发现类名“JaxRsRuntimeException”的异常,然后处理它。但这对我来说似乎不正确。

那么,在这种情况下,WLP 设计是否不会给我 WebApplicationException 呢?处理这种情况的优雅解决方案是什么?

谢谢


编辑:添加了源代码的某些部分。

REST 端点和资源方法:

@Path("/books")
public class BookEndpoint {

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createBook(Book book, @Context UriInfo uriInfo) {
        bookDao.create(book);
        UriBuilder builder = uriInfo.getAbsolutePathBuilder();
        builder.path(Integer.toString(book.getId()));
        return Response.created(builder.build()).entity(book).build();
    }
}

带有 JAXB 注释的实体:

@XmlRootElement
public class Book {

    private int id;
    private String title;

    // getters, setters
}

异常堆栈跟踪:

com.ibm.ws.jaxrs20.JaxRsRuntimeException: java.io.EOFException: No content to map to Object duto end of input
    at org.apache.cxf.jaxrs.utils.JAXRSUtils.toJaxRsRuntimeException(JAXRSUtils.java:1928)
    at [internal classes]
    at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:71)
    at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:201)
    at [internal classes]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.EOFException: No content to map to Object duto end of input
    at org.codehaus.jackson.map.ObjectMapper._initForReading(ObjectMapper.java:2775)
    at [internal classes]
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBodyReader(JAXRSUtils.java:1413)
    at [internal classes]
    ... 48 more
4

2 回答 2

3

这是基于 JAX-RS 2.0 Spec的第 3.3.4 节(和 4.5.1)的预期行为。这些部分描述了如何处理来自 JAX-RS 资源和提供程序的异常 - 简而言之:

  1. 如果异常是 a WebApplicationException,那么它会自动映射到 a Response
  2. 如果有一个ExceptionMapper注册可以处理抛出的异常,那么它将用于生成响应。
  3. 未经检查的异常会传播到容器(即 Liberty 的 JAX-RS 实现代码)。
  4. 未映射的异常必须通过特定于容器的异常进行处理,然后适当地传播到底层容器——在这种情况下,ServletException必须将 a 传递给 Web 容器。

JaxRsRuntimeException用于满足步骤 4 。

在这种情况下,内置 JSON 提供程序(基于 Jackson 1.X)正在抛出EOFException. 由于 EOFException(或其任何超类)没有异常映射器,因此它最终ServletException通过JaxRsRuntimeException.

为了让应用程序处理这种情况,有几个不同的选项:

  1. 您可以注册一个ExceptionMapper特定于此异常类型(EOFException或其任何超类 - 即IOException)的。您不需要为该异常注册映射器,JaxRsRuntimeException因为该异常仅在 Liberty 内部使用 - 并且不应被映射。如果您看到 JaxRsRuntimeException 传递给ExceptionMapper,那么您应该向 IBM 提出支持案例,因为这可能是一个错误。

使用 an时,您可以在提供者或资源抛出ExceptionMapper<EOFException>an 时返回特定响应。EOFException

  1. 您可以注册自己的MessageBodyReader,将 JSON 转换为对象(使用 Jackson 或任何其他 JSON 序列化代码),但它将以您想要的方式处理空消息主体 - 例如,将其转换为null或使用某种默认对象实例。由于用户注册的提供程序优先于内置提供程序,因此将使用此 MBR 代替 Liberty 的基于 Jackson 的 MBR。

这种方法绝对可以让您更好地控制数据的反序列化方式以及异常处理。

  1. 注册一个ContainerRequestFilter在消息正文为空时将中止的提供程序。这是一个例子:

    @Provider
    public class EmptyBodyCheckFilter implements ContainerRequestFilter {
    
        @Override
        public void filter(ContainerRequestContext crc) throws IOException {
            if (crc.getEntityStream().available() < 1) {
                crc.abortWith(Response.status(400).entity("Invalid request - empty message body").build());
            }
        }
    }
    

我已经使用 WebSphere Liberty May 2018 Beta 成功测试了选项 1 和 3。我没有针对这种情况亲自测试过选项 2,但是基于过去使用自定义 MBR,这应该可以工作。

要记住的一件事是,当 Liberty GA 作为该jaxrs-2.1功能时,它将使用 JSONB 作为内置提供程序来序列化/反序列化 JSON,而不是 Jackson。我使用 JAX-RS 2.1(也在 May Beta 中)测试了您的场景EOFException,JSONB 代码不是NoSuchElementException. 如果您认为您可能会迁移到 JAX-RS 2.1,那么我建议您使用选项 2 或 3。选项 1 将要求您ExceptionMapper为 JAX-RS 2.1 创建一个新的。

希望这可以帮助,

安迪

于 2018-05-23T19:16:38.193 回答
2

不是关于“为什么 WLP 包装异常..etc”的直接答案,但可能会像您一样添加一个异常拦截器,但"ExceptionMapper<Exception>"会重复并递归地迭代“原因”以检查是否java.io.EOFException是其中之一......

于 2018-05-22T15:53:09.930 回答