java 基于 JAX-RS 的实现中的简单 REST 资源版本控制?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4924034/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 08:38:09  来源:igfitidea点击:

Easy REST resource versioning in JAX-RS based implementations?

javarestversioningjerseyjax-rs

提问by Volodymyr Tsukur

Best practice for REST resource versioning is putting version information into Accept/Content-Type headers of HTTP request leaving URI intact.

REST 资源版本控制的最佳实践是将版本信息放入 HTTP 请求的 Accept/Content-Type 标头中,保持 URI 不变。

Here is the sample request/response to REST API for retrieving system information:

以下是用于检索系统信息的 REST API 示例请求/响应:

==>
GET /api/system-info HTTP/1.1
Accept: application/vnd.COMPANY.systeminfo-v1+json

<==
HTTP/1.1 200 OK
Content-Type: application/vnd.COMPANY.systeminfo-v1+json
{
  “session-count”: 19
}

Pay attention that version is specified in MIME type.

注意版本是在 MIME 类型中指定的。

Here is another request/response for version 2:

这是版本 2 的另一个请求/响应:

==>
GET /api/system-info HTTP/1.1
Accept: application/vnd.COMPANY.systeminfo-v2+json

<==
HTTP/1.1 200 OK
Content-Type: application/vnd.COMPANY.systeminfo-v2+json
{
  “uptime”: 234564300,
  “session-count”: 19
}

See http://barelyenough.org/blog/tag/rest-versioning/for more explanation and examples.

有关更多解释和示例,请参阅http://barelyenough.org/blog/tag/rest-versioning/

Is it possible to implement this approach easily in Java-targeted JAX-RS based implementations, such as Jersey or Apache CXF?

是否可以在基于 Java 的 JAX-RS 实现(例如 Jersey 或 Apache CXF)中轻松实现这种方法?

The goal is to have several @Resource classes with the same @Path value, but serving the request based on actual version specified in MIME type?

目标是让多个 @Resource 类具有相同的 @Path 值,但根据 MIME 类型中指定的实际版本提供请求?

I've looked into JAX-RS in general and Jersey in particlaur and found no support for that. Jersey doesn't give a chance to register two resources with the same path. Replacement for WebApplicationImpl class needs to implemented to support that.

我总体上研究了 JAX-RS,特别研究了 Jersey,但没有发现对此的支持。Jersey 没有机会使用相同的路径注册两个资源。需要实现 WebApplicationImpl 类的替换来支持它。

Can you suggest something?

你能提出一些建议吗?

NOTE: It is required for multiple versions of the same resource needs to be available simultaneously. New versions may introduce incompatibale changes.

注意:同一资源的多个版本需要同时可用。新版本可能会引入不兼容的更改。

采纳答案by Will Hartung

JAX-RS dispatches to methods annotated with @Produces via the Accept header. So, if you want JAX-RS to do your dispatching, you'll need to leverage this mechanism. Without any extra work, you would have to create a method (and Provider) for every media type you wish to support.

JAX-RS 通过 Accept 头分派到用 @Produces 注释的方法。因此,如果您希望 JAX-RS 进行调度,则需要利用此机制。无需任何额外工作,您就必须为您希望支持的每种媒体类型创建一个方法(和提供者)。

There's nothing stopping you from having several methods based on media type that all call a common method to do that work, but you'd have to update that and add code every time you added a new media type.

没有什么能阻止您拥有多种基于媒体类型的方法,它们都调用一个通用方法来完成这项工作,但是每次添加新媒体类型时,您都必须更新它并添加代码。

One idea is to add a filter that "normalizes" your Accept header specifically for dispatch. That is, perhaps, taking your:

一个想法是添加一个过滤器,专门为调度“规范化”您的 Accept 标头。也就是说,也许,将您的:

Accept: application/vnd.COMPANY.systeminfo-v1+json

And converting that to, simply:

并将其转换为:

Accept: application/vnd.COMPANY.systeminfo+json

At the same time, you extract the version information for later use (perhaps in the request, or some other ad hoc mechanism).

同时,您提取版本信息供以后使用(可能在请求中,或其他一些特殊机制中)。

Then, JAX-RS will dispatch to the single method that handles "application/vnd.COMPANY.systeminfo+json".

然后,JAX-RS 将分派到处理“application/vnd.COMPANY.systeminfo+json”的单个方法。

THAT method then takes the "out of band" versioning information to handle details in processing (such as selecting the proper class to load via OSGi).

然后,该方法采用“带外”版本信息来处理处理中的细节(例如选择合适的类以通过 OSGi 加载)。

Next, you then create a Provider with an appropriate MessageBodyWriter. The provider will be selected by JAX-RS for the application/vnd.COMPANY.systeminfo+json media type. It will be up to your MBW to figure out the actual media type (based again on that version information) and to create the proper output format (again, perhaps dispatching to the correct OSGi loaded class).

接下来,您将使用适当的 MessageBodyWriter 创建一个 Provider。JAX-RS 将为 application/vnd.COMPANY.systeminfo+json 媒体类型选择提供者。由您的 MBW 来确定实际的媒体类型(再次基于该版本信息)并创建正确的输出格式(同样,可能分派到正确的 OSGi 加载类)。

I don't know if an MBW can overwrite the Content-Type header or not. If not, then you can delegate the earlier filter to rewrite that part for you on the way out.

我不知道 MBW 是否可以覆盖 Content-Type 标头。如果没有,那么您可以委托较早的过滤器在退出时为您重写该部分。

It's a little convoluted, but if you want to leverage JAX-RS dispatch, and not create methods for every version of your media type, then this is a possible path to do that.

这有点令人费解,但如果您想利用 JAX-RS 调度,而不是为您的媒体类型的每个版本都创建方法,那么这是一个可能的途径。

Edit in response to comment:

编辑以回应评论:

Yea, essentially, you want JAX-RS to dispatch to the proper class based on both Path and Accept type. It is unlikely that JAX-RS will do this out of the box, as it's a bit of an edge case. I have not looked at any of the JAX-RS implementations, but you may be able to do what you want by tweaking one of the at the infrastructure level.

是的,本质上,您希望 JAX-RS 基于 Path 和 Accept 类型分派到正确的类。JAX-RS 不太可能开箱即用,因为它有点边缘情况。我没有研究过任何 JAX-RS 实现,但是您可以通过在基础设施级别调整其中一个来做您想做的事情。

Possibly another less invasive option is to use an age old trick from the Apache world, and simply create a filter that rewrites your path based on the Accept header.

可能另一个侵入性较小的选择是使用来自 Apache 世界的古老技巧,并简单地创建一个过滤器,根据 Accept 标头重写您的路径。

So, when the system gets:

所以,当系统得到:

GET /resource
Accept: application/vnd.COMPANY.systeminfo-v1+json

You rewrite it to:

您将其重写为:

GET /resource-v1
Accept: application/vnd.COMPANY.systeminfo-v1+json

Then, in your JAX-RS class:

然后,在您的 JAX-RS 类中:

@Path("resource-v1")
@Produces("application/vnd.COMPANY.systeminfo-v1+json")
public class ResourceV1 {
    ...
}

So, your clients get the correct view, but your classes get dispatched properly by JAX-RS. The only other issue is that your classes, if they look, will see the modified Path, not the original path (but your filter can stuff that in the request as a reference if you like).

因此,您的客户端会获得正确的视图,但 JAX-RS 会正确调度您的类。唯一的另一个问题是,您的类(如果它们看起来)将看到修改后的路径,而不是原始路径(但如果您愿意,您的过滤器可以将其填充到请求中作为参考)。

It's not ideal, but it's (mostly) free.

这并不理想,但它(大部分)是免费的。

Thisis an existing filter that might do what you want to do, if not it perhaps can act as an inspiration for you to do it yourself.

是一个现有的过滤器,可能会做你想做的事情,如果没有,它也许可以作为你自己做的灵感。

回答by Andreas Lundgren

With current version of Jersey, I would suggest an implementation with two different API methods and two different return values that are automatically serialised to the applicable MIME type. Once the requests to the different versions of the API are received, common code can be used underneath.

对于当前版本的 Jersey,我建议使用两种不同的 API 方法和两种不同的返回值来实现,这些返回值会自动序列化为适用的 MIME 类型。一旦收到对不同版本 API 的请求,就可以在下面使用通用代码。

Example:

例子:

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;

@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public VersionOneDTO get(@PathParam("id") final String id) {

    return new VersionOneDTO( ... );

}

@GET
@Path("/{id}")
@Produces("application/vnd.COMPANY.systeminfo-v2+json;qs=0.9")
public VersionTwoDTO get_v2(@PathParam("id") final String id) {

    return new VersionTwoDTO( ... );

}

If method get(...)and get_v2(...)use common logic, I would suggest to put that in a common private method if it's API related (such as session or JWT handling) or else in a common public method of a Service Layer that you access via inheritance or Dependency Injection. By having two different methods with different return types, you ensure that the structure returned is of correct type for the different versions of the API.

如果方法get(...)get_v2(...)使用通用逻辑,如果它与 API 相关(例如会话或 JWT 处理),我建议将其放在通用私有方法中,或者放在您通过继承或依赖注入访问的服务层的通用公共方法中。通过使用具有不同返回类型的两种不同方法,您可以确保返回的结构对于不同版本的 API 具有正确的类型。

Note that some old client may not specify Accept header at all. That means implicitly that they would accept any content type, thus any version of your API. In practice, this is most often not the truth. For this reason you should specify a weight to newer versions of the API using the qsextension of the MIME type as shown in the @Producesannotation in the example above.

请注意,某些旧客户端可能根本不指定 Accept 标头。这意味着他们将接受任何内容类型,即您的 API 的任何版本。在实践中,这通常不是事实。出于这个原因,您应该使用qsMIME 类型的扩展为新版本的 API 指定权重,如上例中的@Produces注释所示。

If you are testing with restAssuredit would look something like this:

如果您使用restAssured它进行测试,它将看起来像这样:

import static com.jayway.restassured.RestAssured.get;
import static com.jayway.restassured.RestAssured.given;

@Test
public void testGetEntityV1() {
    given()
        .header("Accept", MediaType.APPLICATION_JSON)
    .when()
        .get("/basepath/1")
    .then()
        .assertThat()
        ... // Some check that Version 1 was called
    ;
}

@Test
public void testGetEntityV1OldClientNoAcceptHeader() {
    get("/basepath/1")
        .then()
        .assertThat()
        ... // Some check that Version 1 was called
    ;
}

@Test
public void testGetEntityV2() {
    given()
        .header("Accept", "application/vnd.COMPANY.systeminfo-v2+json")
    .when()
        .get("/basepath/1")
    .then()
        .assertThat()
        ... // Some check that Version 2 was called
    ;
}

回答by cghislai

You should be able to use different classes with the same path provided they consume/produce different media types. So this should work with any jax-rs provider:

您应该能够使用具有相同路径的不同类,前提是它们使用/产生不同的媒体类型。所以这应该适用于任何 jax-rs 提供者:

@Path("/api/system-info")
@Consumes("application/vnd.COMPANY.systeminfo-v1+json")
@Produces("application/vnd.COMPANY.systeminfo-v1+json")
public class SystemInfoResourceV1 {
}

and

@Path("/api/system-info")
@Consumes("application/vnd.COMPANY.systeminfo-v2+json")
@Produces("application/vnd.COMPANY.systeminfo-v2+json")
public class SystemInfoResourceV2 {
}

回答by Donal Fellows

If you're using CXF, you could use the technique specified hereto build a new serialization provider (building off the existing infrastructure) which produces the data in the specific format desired. Declare a couple of those, one for each specific format that you want, and use the @Producesannotation to let the machinery handle the rest of the negotiation for you, though it might also be an idea to support the standard JSON content type too so that normal clients can handle it without needing to grok your specialness. The only real question then becomes what is the best way to do the serialization; I presume you can figure that out for yourself…

如果您正在使用 CXF,您可以使用此处指定的技术构建一个新的序列化提供程序(在现有基础设施的基础上构建),以所需的特定格式生成数据。声明其中一些,针对您想要的每种特定格式,并使用@Produces注释让机器为您处理其余的协商,尽管支持标准 JSON 内容类型也可能是一个想法,以便正常客户可以处理它,而无需了解您的特殊性。唯一真正的问题变成了进行序列化的最佳方法是什么;我想你可以自己弄清楚……



[EDIT]: Further digging in the CXF documentationleads to the revelation that both the @Consumesand @Producesannotations are considered to be axes for doing selection. If you want to have two methods that handle the production of the response for different media types, you most certainly can. (You'll have to add the serialization and/or deserialization providers if you're using custom types, but you can do the delegation of the majority of the work to the standard providers.) I'd still like to caution that you should still ensure that the resource indicated by the path should be the same in both cases; to do otherwise is not RESTful.

[编辑]:对CXF 文档的进一步挖掘揭示了 the@Consumes@Producesannotations 都被认为是进行选择的轴。如果您希望有两种方法来处理针对不同媒体类型的响应的生成,那么您当然可以。(如果您使用自定义类型,则必须添加序列化和/或反序列化提供程序,但您可以将大部分工作委托给标准提供程序。)我仍然想提醒您应该仍然确保路径指示的资源在两种情况下都应该相同;否则不是 RESTful。

回答by Diego Dias

One possible solution is to use one @Path with

一种可能的解决方案是使用一个@Path

Content-Type: application/vnd.COMPANY.systeminfo-{version}+json

内容类型:application/vnd.COMPANY.systeminfo-{version}+json

Then, inside the method of the given @Path you can call the version of the WebService

然后,在给定的@Path 的方法中,您可以调用 WebService 的版本