RestEasy ExceptionMapper NotAllowedException序列化错误MediaType八位字节流

时间:2017-01-13 17:20:17

标签: rest resteasy java-ee-7 wildfly-10

我在这里提供简单的休息服务来说明我收到的例外。

服务 A

@Path("/A")
public class ServiceA {

    @GET
    @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response show() {
        return Response.ok(new User("John", "Doe")).build();
    }
}

型号:

用户

@XmlRootElement
public class User {

    private String firstName;
    private String lastName;

    public User() {
    }

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

错误响应

@XmlRootElement
public class ErrorResponse {

    private String errorType;
    private String errorMessage;

    public ErrorResponse() {
    }

    public ErrorResponse(String errorType, String errorMessage) {
        this.errorType = errorType;
        this.errorMessage = errorMessage;
    }

    public String getErrorType() {
        return errorType;
    }

    public void setErrorType(String errorType) {
        this.errorType = errorType;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }
}

最后,我的 ExceptionMapper 如下所示:

@Provider
public class GenericExceptionMapper implements ExceptionMapper<Exception> {

    private final Logger LOG = LoggerFactory.getLogger(GenericExceptionMapper.class);

    @Override
    public Response toResponse(Exception exception) {
        ErrorResponse errorResponse = new ErrorResponse(exception.getClass().getSimpleName(), exception.getMessage());

        if (exception instanceof WebApplicationException) {
            LOG.error("Type: {}", exception.getClass().getSimpleName());
            LOG.error("Message: {}", exception.getMessage());
            WebApplicationException webApplicationException = (WebApplicationException) exception;
            return Response.status(webApplicationException.getResponse().getStatus()).entity(errorResponse).build();
        }

        return Response.serverError().entity(errorResponse).build();
    }
}

在URI上调用GET我得到了正确的答案:

Accept: application/json
GET http://localhost:8080/exception-mapper-example/rest/A

{"firstName":"John","lastName":"Doe"}

然而,在这个URI上调用POST我得到一个例外:

 2017-01-13 16:53:46,859 ERROR [com.aizaz.samples.exceptionmapper.GenericExceptionMapper] (default task-35) Type: NotAllowedException
 2017-01-13 16:53:46,860 ERROR [com.aizaz.samples.exceptionmapper.GenericExceptionMapper] (default task-35) Message: RESTEASY003650: No resource method found for POST, return 405 with Allow header
 2017-01-13 16:53:46,860 ERROR [io.undertow.request] (default task-35) UT005023: Exception handling request to /exception-mapper-example/rest/A: org.jboss.resteasy.spi.UnhandledException: org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response object of type: com.aizaz.samples.model.ErrorResponse of media type: application/octet-stream
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:180)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:199)
at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:221)
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85)
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131)
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:284)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:263)
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:174)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:202)
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:793)
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: org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response object of type: com.aizaz.samples.model.ErrorResponse of media type: application/octet-stream
at org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:66)
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:176)
... 32 more

相关部分

Caused by: org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure:
Could not find MessageBodyWriter for response object of type:
com.aizaz.samples.model.ErrorResponse of media type: application/octet-stream

由于媒体类型:application / octet-stream,显然序列化失败。

我知道在构建响应时我可以明确指定媒体类型,例如

 Response.ok().type(MediaType.APPLICATION_JSON).build();

但我不想这样做;因为我接受JSON / XML Accept标头,并希望以JSON或XML格式发回适当的响应。

  1. 我怎样才能做到这一点?
  2. 为什么在这种情况下使用媒体类型octet-stream创建响应?
  3. 我的意思是,如果我创建自定义异常,它由代码中描述的相同ExceptionMapper映射; Response对象不需要显式指定 MediaType

    如果有人可以提供他/她的宝贵智慧,那将是非常好的

2 个答案:

答案 0 :(得分:0)

您可以使用ExceptionMapper JAX-RS API管理HttpHeaders返回的相应格式,并获取请求实体的MediaType,参见javadoc:{{3} }

所以你的代码如下:

@Provider
public class GenericExceptionMapper implements ExceptionMapper<Exception> {

    @Context
    private HttpHeaders m_headers;

    private final Logger LOG = LoggerFactory.getLogger(GenericExceptionMapper.class);

    @Override
    public Response toResponse(Exception exception) {
        ErrorResponse errorResponse = new ErrorResponse(exception.getClass().getSimpleName(), exception.getMessage());

        if (exception instanceof WebApplicationException) {
            LOG.error("Type: {}", exception.getClass().getSimpleName());
            LOG.error("Message: {}", exception.getMessage());
            WebApplicationException webApplicationException = (WebApplicationException) exception;
            return Response.status(webApplicationException.getResponse().getStatus()).entity(errorResponse).build();
        }

        return Response.serverError().entity(errorResponse).type(m_headers.getMediaType()).build();
    }
}

答案 1 :(得分:0)

如果自定义异常扩展WebApplicationException,例如

public class MyCustomException extends WebApplicationException 

不需要显式ExceptionMapper<MyCustomerException>来进行异常处理和响应创建。

ExceptionMapper可以非常有用地处理从Exception(或其子类)而不是WebApplicationException(及其子类)派生的异常(注意WebApplicationException也是{的子类) {1}})

例如,ExceptionMapper可用于处理Exception等异常并创建响应。

在上述两种情况下,响应都可以根据Resource方法中指定的IllegalArgumentException进行序列化。

然而,在查看RestEasy的规范实现之后,我发现,即使是@Produces(s),如果ExceptionMapper是其余服务的WebApplicationException,它也会被触发。

@Provided

因此我要么确保ExceptionMapper用于某些特定的异常,例如resteasy-jaxrs:3.1.0.Final class: ExceptionHandler method: public Response handleException(HttpRequest request, Throwable e) // First try and handle it with a mapper if ((jaxrsResponse = executeExceptionMapper(e)) != null) { return jaxrsResponse; } ,而不是捕获代码ExceptionMapper<IllegalArgumentException>中所示的所有异常,或者只是返回响应,如下面的代码所示:

ExceptionMapper<Exception>

不会发生序列化错误。为什么?因为框架负责这一点(基于 if (exception instanceof WebApplicationException) { WebApplicationException webApplicationException = (WebApplicationException) exception; return webApplicationException.getResponse(); } 注释,它将序列化基于 NON WebApplicationException的响应的响应。对于基于@Produces的响应,如上所示,框架将采用关注响应(因为从未使用过ErrorResponse实体)

然而,这个故障中提到的问题。 WebApplicationException出现在规范实现代码之前与URI关联的方法被执行。因此,NotAllowedException注释不会生效,并且在对响应进行编组时,会使用默认的MediaType八位字节流。

@Produces

因此,虽然发生以下异常; DefaultOptionsMethodException NotAllowedException NotSupportedException异常 NotAcceptableException

请求属性resteasy-jaxrs:3.1.0.Final class: SegmentNode method: public Match match(List<Match> matches, String httpMethod, HttpRequest request) 未设置

RESTEASY_CHOSEN_ACCEPT

当服务器尝试编写响应时,它找不到MediaType(因为我们在创建响应对象时从未设置过它)

  request.setAttribute(RESTEASY_CHOSEN_ACCEPT, sortEntry.getAcceptType());
  return sortEntry.match;

尝试从方法注释中设置它;之前提到过的内容从未设置过,因为resteasy-jaxrs:3.1.0.Final class: ServerResponseWriter method: public static void writeNomapResponse(BuiltResponse jaxrsResponse, final HttpRequest request, ... if (jaxrsResponse.getEntity() != null && jaxrsResponse.getMediaType() == null) { setDefaultContentType(request, jaxrsResponse, providerFactory, method); } 可能已经设置NotAllowedException

它找到一张外卡,因为没有指定接受标题,因此设置了八位字节流

RESTEASY_CHOSEN_ACCEPT

这只是一个摘要;对于详细步骤,我必须回到规范实现代码

因此,我必须指定一种媒体类型。这样做可以查看来自resteasy-jaxrs:3.1.0.Final class: ServerResponseWriter method: protected static void setDefaultContentType(HttpRequest request, BuiltResponse ... if (chosen.isWildcardType()) { chosen = MediaType.APPLICATION_OCTET_STREAM_TYPE; } 的{​​{1}}以使其成为动态,或者如果在我的情况下标题中没有指定任何内容,我提供了一个默认的MediaType HttpHeaders来进行序列化。< / p>

希望这有助于某人也面临同样的问题。