RestEasy ExceptionMapper不允许异常序列化错误MediaType八位字节流

RestEasy ExceptionMapper不允许异常序列化错误MediaType八位字节流,rest,resteasy,java-ee-7,wildfly-10,Rest,Resteasy,Java Ee 7,Wildfly 10,我在这里展示了简单的rest服务来说明我收到的异常 服务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(); } } 型号:

我在这里展示了简单的rest服务来说明我收到的异常

服务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;
    }
}
最后,我的例外外观如下所示:

@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上调用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/XMLAccept头,并希望以JSON或XML格式返回适当的响应

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


    如果有人能向我提供他/她的宝贵智慧,那就太好了。您可以使用JAX-RS API的
    HttpHeaders
    管理
    ExceptionMapper
    返回的适当格式,并获取请求实体的
    MediaType
    ,参见javadoc:

    因此,您的代码如下所示:

    @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();
        }
    }
    
    @Provider
    公共类GenericeExceptionMapper实现ExceptionMapper{
    @上下文
    私有HttpHeader m_头;
    私有最终记录器LOG=LoggerFactory.getLogger(genericeExceptionMapper.class);
    @凌驾
    公众响应(例外){
    ErrorResponse ErrorResponse=新的ErrorResponse(exception.getClass().getSimpleName(),exception.getMessage());
    if(WebApplicationException的异常实例){
    LOG.error(“Type:{}”,exception.getClass().getSimpleName());
    错误(“消息:{}”,exception.getMessage());
    WebApplicationException WebApplicationException=(WebApplicationException)异常;
    返回Response.status(webApplicationException.getResponse().getStatus()).entity(errorResponse.build();
    }
    返回Response.serverError().entity(errorResponse.type(m_headers.getMediaType()).build();
    }
    }
    
    如果自定义异常扩展了
    WebApplicationException
    ,例如

    public class MyCustomException extends WebApplicationException 
    
    不需要显式的异常处理和响应创建

    ExceptionApper可以非常有助于处理从
    Exception
    (或其子类)而不是
    WebApplicationException
    (及其子类)派生的异常(注意
    WebApplicationException
    也是
    Exception
    的子类)

    例如,ExceptionMapper可用于处理异常,如
    IllegalArgumentException
    和创建响应

    在上述两种情况下,可以根据Resource方法上指定的
    @products
    序列化响应

    然而,在查看RestEasy的规范实现后,我发现,即使对于
    WebApplicationException
    (s),如果rest服务提供了一个异常apper,它也会被触发

    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用于某些特定的异常,例如
    ExceptionMapper
    ,而不是捕获代码
    ExceptionMapper
    中如上所示的所有异常,要么返回如下代码所示的响应:

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

    但是,出现了此票证中提到的问题。
    NotAllowedException
    在执行与URI关联的方法之前,在规范实现代码中发生。因此,
    @生成的注释不会生效,并且在封送响应时,使用默认的MediaType八位字节流

    resteasy-jaxrs:3.1.0.Final
    class: SegmentNode
    method: public Match match(List<Match> matches, String httpMethod, HttpRequest request)
    
    当服务器尝试写入响应时,它找不到MediaType(因为我们在创建响应对象时从未设置它)

    它试图从方法注释中设置它;正如前面提到的,方法注释从未设置过,因为
    NotAllowedException
    发生在
    RESTEASY\u selected\u 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;
    }
    
    (这只是一个总结;对于详细的步骤,我必须回到规范实现代码)

    因此,我必须指定一个mediatype。这可以通过查看
    @Context
    中的
    HttpHeaders
    来实现,以使其成为动态的,或者如果在头中没有指定任何内容(如我的情况),我将提供一个默认的mediatype
    应用程序/XML
    ,以便继续序列化


    希望这也能帮助面临同样问题的人。

    感谢您的回复。我碰巧调试了RestEasy规范的实现以了解问题。您的解决方案只有在请求头中设置了ContentType时才有效。这意味着,我需要显式指定始终指定内容类型的客户端。请注意,如果出现异常从资源中抛出;在构造响应对象时不需要指定mediatype。原因是如果请求中未指定内容类型,则注释“生成”足够找到MessageBodyWriter进行封送处理;但是NotAllowedException是特殊的。在这种情况下,您可以通过m_头获取可接受的媒体类型。getAcceptableMediaTypes()。查看此线程:
      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);
     }
    
    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;
    }