在所有 JSON 生产端点上使用 @Produces("application/json") 是好的做法吗?

Is it good practice to use @Produces("application/json") on all JSON producing endpoints?

我们开始将 Jersey/JAX-RS 用于我们的 front-end 代码使用的内部 REST 端点。必须 return 结果的端点总是发送 JSON objects.

出于调试目的,我们使用 firefox restclient 扩展。直到最近,我只会输入 URL 并点击发送,然后会返回显示为 JSON.

的内容

但是当我今天早上这样做时,FF 扩展返回并告诉我必须将响应类型更改为二进制 (BLOB)。这样做会导致显示编码字符串而不是 JSON.

我可以通过设置请求 header(Accept:application/json)来解决这个问题。

在进行更多研究后,我发现了这个 question。我的 结论 是:我们可能应该将 @Produces("application/json") 添加到所有这些端点。

问题:真的那么简单,还是有充分的技术理由这样做?

Is it good practice to use @Produces("application/json") on all JSON producing endpoints?

如果您的资源方法产生 JSON 作为资源的表示,它们 应该 @Produces(MediaType.APPLICATION_JSON) 注释。因此,响应将有一个 Content-Type header 指示有效负载的媒体类型。

@Produces annotation is also used for request matching: The JAX-RS runtime matches the media type sent in the Accept header with the media type defined in the @Produces 注释。


如果您不想注释应用程序中的每个资源方法,您可以改为注释资源 classes。它将指示在此类 class 中定义的所有方法必须生成 JSON 作为您的资源的表示。


在应用程序中注册的 @Produces annotation indicates the media type that will be produced by the MessageBodyWriter 个实例中定义的媒体类型。考虑以下示例:

@GET
@Produces(MediaType.APPLICATION_JSON)
public Foo getFoo() {
    Foo foo = new Foo();
    return Response.ok(foo).build();
}

一旦 getFoo() 方法被 @Produces(MediaType.APPLICATION_JSON) 注释,JAX-RS 将把 Foo 实例写入 JSON 文档。它在 MessageBodyWriter implementation. If your application uses Jackson, for example, the JacksonJsonProvider 中完成,将用于将 Java objects 转换为 JSON 文件。

为了Content Negotiation 和 HTTP 协议的正确性。如果没有这些注释,结果将取决于客户端请求和服务器的默认行为(在不同的实现中可能不同),这会导致不可预测和不明确的结果。

通过这些注释,我们宣传我们可以生产和消费哪些媒体类型。在检索 (GET) 请求中,客户端应发送 Accept header 以及他们期望返回的资源的媒体类型。在创建请求(PUT,POST)时,客户端应该发送 Content-Type header 告诉服务器他们发送的数据是什么媒体类型。如果这些 header 与服务器宣传要处理的内容不匹配,那么客户端将收到错误响应,告诉他们问题出在哪里;使用检索请求和 non-matching Accept header,响应将是 406 Not Acceptable. With a Create request and a non-matching Content-Type header, the response will be a 415 Unsupported Media Type.

这就是内容协商的工作原理。为了确保我们的服务器按照客户的期望运行,我们应该声明我们可以在服务器上处理什么。注释就是这样做的。

如您所述,当您离开 @Produces 时,客户告诉您需要更改响应类型。这是因为结果是 Content-Type 响应 header 被设置为 application/octet-stream,这就是 the answers here 的结论。客户端使用 Content-Type header 来确定如何处理响应。

最后一个示例是针对检索请求的。如果我们在 Create 端点上离开 @Consumes,很多不同的事情都会出错。举个例子,我们有一个我们想要接受 JSON 的端点,所以我们创建一个 POJO 来将 JSON 映射到。

@POST
public Response create(Customer customer) {}

要使其正常工作,取决于客户端将请求中的 Content-Type header 设置为 application/json。但是如果没有 @Consumes 注释,我们基本上是在宣传这个端点能够接受 any 媒体类型,这太荒谬了。 @Consumes 注释就像守卫说 "If you don't send the right type of data, you cannot pass"。但是由于我们没有守卫,所有的数据都被允许通过,结果是不可预测的,因为根据客户端设置 Content-Type 的内容,我们不知道 MessageBodyReader1 将处理从实体 body 到 Customer 的转换。如果没有选择正确的 MessageBodyReader(将 JSON 转换为 POPJO 的那个),那么很可能会导致异常,并且客户端将返回 500 Internal Server Error,这不是与获得 415 不支持的媒体类型一样具体。


1. See chapter 8 and 9 of the Jersey docs。它将解释如何分别使用 MessageBodyReaderMessageBodyWriter 将实体主体转换为 Java objects(反之亦然)。