如何在 JAX-RS 网络服务中 return 响应 XML?

How to return Response in XML in JAX-RS web service?

我正在使用 JAX-RSJava 6Jersey 开发 REST 服务1.8 我的服务器是 JBoss 5.1.0 GA,我从一个简单的端点开始,它接收一个参数和 return 一个 null价值。

this article 之后,我使用 ExceptionMapper 将我预期的异常映射到三个 classes:AppException项目范围内的异常,NotFoundException 用于未找到记录的异常,GenericException 用于预期错误之外的任何其他异常。

我可以 return 异常作为 application/json 响应就好了,但是当我尝试 return 响应作为 application/xml,我从我的服务器获得一个 HTML 页面,其中包含 HTTP 500 状态代码。

例如,如果我没有将所需的参数发送到我的端点,我会收到 HTTP 400 状态代码及其 JSON 正确响应:

{
    "status": 400,
    "message": "org.xml.sax.SAXParseException: Premature end of file.",
    "developerMessage": "javax.ws.rs.WebApplicationException: org.xml.sax.SAXParseException: Premature end of file.... 40 more\r\n"
}

但如果我尝试 return 一个 XML 响应,我会得到这个 HTML 页面:

<html>
    <head>
        <title>JBoss Web/2.1.3.GA - Informe de Error</title>
    </head>
    <body>
        <h1>Estado HTTP 500 - </h1>
        <HR size="1" noshade="noshade">
            <p>
                <b>type</b> Informe de estado
            </p>
            <p>
                <b>mensaje</b>
                <u></u>
            </p>
            <p>
                <b>descripción</b>
                <u>El servidor encontró un error interno () que hizo que no pudiera rellenar este requerimiento.</u>
            </p>
            <HR size="1" noshade="noshade">
                <h3>JBoss Web/2.1.3.GA</h3>
     </body>
  </html>

我的 ErrorMessage class 看起来和我链接的文章中描述的一样:

import java.lang.reflect.InvocationTargetException;

import javax.ws.rs.core.Response;

import org.apache.commons.beanutils.*;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import com.sun.jersey.api.NotFoundException;

@XmlRootElement(name = "errorMessage")
public class ErrorMessage {

    @XmlElement(name = "status")
    int status;

    @XmlElement(name = "message")
    String message;

    @XmlElement(name = "developerMessage")
    String developerMessage;

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getDeveloperMessage() {
        return developerMessage;
    }

    public void setDeveloperMessage(String developerMessage) {
        this.developerMessage = developerMessage;
    }

    public ErrorMessage(AppException ex) {
        try {
            BeanUtils.copyProperties(this, ex);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public ErrorMessage(NotFoundException ex) {
        this.status = Response.Status.NOT_FOUND.getStatusCode();
        this.message = ex.getMessage();
    }

    public ErrorMessage() {
    }

    @Override
    public String toString(){
        return "ErrorMessage{" + "status=" + status + ", message='" + message + '\'' + ", developerMessage='" + developerMessage + "'}";
    }
}

为什么在指定时我无法 return XML 响应?

这是我正在使用的 GenericExceptionMapper class

@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {

    @Override
    public Response toResponse(Throwable exception) {

        ErrorMessage errorMessage = new ErrorMessage();     
        setHttpStatus(exception, errorMessage);
        errorMessage.setMessage(exception.getMessage());
        StringWriter errorStackTrace = new StringWriter();
        exception.printStackTrace(new PrintWriter(errorStackTrace));
        errorMessage.setDeveloperMessage(errorStackTrace.toString());
        // The only change is the MediaType.APPLICATION_XML, originally it was MediaType.APPLICATION_JSON
        return Response.status(errorMessage.getStatus()).entity(errorMessage).type(MediaType.APPLICATION_XML).build();
    }
    private void setHttpStatus(Throwable ex, ErrorMessage errorMessage) {
        if(ex instanceof WebApplicationException ) { 
            errorMessage.setStatus(((WebApplicationException)ex).getResponse().getStatus());
        } else {
            errorMessage.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
        }
    }

}

在您的 ErrorMessage class 上方提供 @XmlAccessorType(XmlAccessType.FIELD) 应该可以解决问题:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ErrorMessage {

    /** contains the same HTTP Status code returned by the server */
    @XmlElement(name = "status")
    int status;

此 JAXB 注释将告诉服务器将属性而不是 getter 绑定到您的 XML。否则会产生错误,因为这会导致您的 XML 响应具有 XML 绑定的重复属性(如重复的代码元素)。

为什么?

By default, if @XmlAccessorType on a class is absent, and none of its super classes is annotated with @XmlAccessorType, then the following default on the class is assumed:

@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)

接下来,如果我们查看有关 XmlAccessType 的文档以了解 PUBLIC_MEMBER 以下是它的内容:

Every public getter/setter pair and every public field will be automatically bound to XML, unless annotated by XmlTransient.

因此 ErrorMessage 的代码变得冲突,因为 XmlElement 也用于将 JavaBean 属性 映射到 XML。因此,例如 属性 code 不仅受 XmlElement 的约束,而且受其 getter 方法的约束。

为了克服这个问题,我们有两个选择:

选项1:我们可以使用XmlAccessType.FIELD这样只使用字段而省略getter。

选项 2:简单地从 class 中删除 XmlElement 注释,在这种情况下 getter 将仅决定绑定。

此外,不要忘记更新映射器 class 的 toResponse 消息以反映输出为 XML:

@Override
    public Response toResponse(AppException ex) {
        return Response.status(ex.getStatus())
                .entity(new ErrorMessage(ex))
                .type(MediaType.APPLICATION_XML).
                build();
    }