java官方 jax rs_关于Java:在基于JAX-RS的实现中轻松进行REST资源版本控制?

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

这是对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

}

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

这是版本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

}

有关更多说明和示例,请参见http://barelyenough.org/blog/tag/rest-versioning/。

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

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

我研究了JAX-RS的整体知识,特别是Jersey的内容,没有找到任何支持。泽西岛没有机会用相同的路径注册两个资源。需要实现对WebApplicationImpl类的替换以支持该替换。

你能建议点什么吗?

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

这绝对不是对API进行版本控制的最佳实践。最佳做法是不要使用版本,而只能进行兼容的更改。在我的书中,为每个明智的客户端应自动处理的更改而人为地创建新的MIME类型(将新的标签/密钥添加到您的数据中)并不是完全RESTful的。

好吧,并非总是可能进行兼容的更改。而且,就我而言,需要同时支持多个REST资源版本。至于必须保留的资源标识,URI必须更改相同。新版本是资源的新表示形式,即新的MIME类型。

谢谢您的评论,我会更新原始问题以使其更具体

@Jochen理想情况下,您永远不必版本。那应该是目标。但是,如果确实有必要,那么我会说这是处理它的最佳方法。

有必要对此进行评论有点太宽泛了。如果您完全更改了演示文稿和路径,那么必须具有新的MIME类型。如果您遵循HATEOAS范式,则可以大大减轻这种痛苦。 (即,不要假设网址的结构,让超文本指导您,等等)

我不同意应该避免版本控制:但是,这里可能存在沟通不畅的情况。我对最佳做法的理解是,给定的URI始终引用特定的不可变版本。并且使用不同的URI处理版本控制。通常通过使用版本标识符作为路径的一部分来完成此操作。

@Jochen我们确实完全更改了演示文稿,但希望坚持使用相同的资源URI

对于原始问题,了解JAX-RS如何适合基于MIME类型和内容协商的版本控制更为重要。

感谢您的澄清。如果由于客户无法处理不同版本的演示文稿而需要保留不同版本的演示文稿,则可能需要采取这种做法。我当然希望避免这种情况。不必维护,记录和调试不同的版本就没什么好玩的了。

是的,它并不是那么容易支持,但是由于领域的特殊性而不可避免。谢谢您的意见!

@StaxMan Nah,URL中的版本控制更加有害。

Darrel:例如,这正是IETF以及许多公共Web服务规范使用URL的方式。这是一个明显而简单的版本控制解决方案。那里什么都没有邪恶。它可能不适用于此用例,但其他人都在使用它。

REST中的@StaxMan资源URI应该是永久链接,并且不应更改(当然,除非它不是一个全新的资源)。版本本质上是不同的表示形式。我们要坚持该规则,除非它执行起来太复杂。谢谢您的意见!

@Volodymyr我可以看到被认为是表示形式的版本与新资源之间的区别-两者都有其用例。关键是调用者然后显式定义信息以获取准确的版本(即,没有人"意外升级")。所以这对我也很有意义。

是的,呼叫者指定了确切的版本。没有重大的意外升级。

@StaxMan关于以MIME类型进行版本的好处是,您可以将URL传递到API较新版本上的其他客户端,并且该客户端仍然可以使用。 (这只是一个明显有益的例子)

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

并没有阻止您使用几种基于媒体类型的方法来调用所有通用方法来完成此工作的方法,但是每次添加新的媒体类型时,您都必须对其进行更新并添加代码。

一种想法是添加一个过滤器,以"标准化"您的Accept标头,专门用于分派。也就是说,可能是您:

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

并将其转换为:

Accept: application/vnd.COMPANY.systeminfo+json

同时,您提取版本信息以供以后使用(也许在请求中,或者在其他临时机制中)。

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

然后,该方法使用"带外"版本控制信息来处理处理中的详细信息(例如,选择适当的类以通过OSGi加载)。

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

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

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

编辑以回应评论:

是的,从本质上讲,您希望JAX-RS基于Path和Accept类型将其分派到适当的类。 JAX-RS不太可能开箱即用,因为这是一个极端的情况。我没有看过任何JAX-RS的实现,但是您可以通过在基础结构级别上调整一个来实现您想要的。

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

因此,当系统得到:

GET /resource

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

您将其重写为:

GET /resource-v1

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

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

@Path("resource-v1")

@Produces("application/vnd.COMPANY.systeminfo-v1+json")

public class ResourceV1 {

...

}

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

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

这是一个现有的过滤器,可能会做您想做的事情,如果没有,它可能会启发您自己做。

非常感谢您的回答。这越来越接近我的需求了。转换MIME将完成任务。但是我不希望Resource方法处理版本信息。整个资源类应代表特定的资源版本,并且在运行时中将有多个资源类,例如捆绑软件1.0中的RestService和捆绑软件2.0中的RestService,都具有@Path(/ rest)。我想指示Jersey区分两者,而不要尽可能重写WebApplicationImpl。 (如果不可能的话,我将继续并重写/扩展它)

到目前为止,这可能是最好的答案。理想情况下,我的类不会在@ Path,@ Produces和资源类名称中具有版本,因为该版本应来自OSGi捆绑软件版本说明符。但同样,这就是理想的情况。您在这里给了您非常有用的提示。谢谢!

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

例:

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( ... );

}

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

请注意,某些旧客户端可能根本不指定Accept标头。这就意味着他们将接受任何内容类型,也就是任何版本的API。实际上,这通常不是事实。因此,您应该使用MIME类型的qs扩展名指定较新版本的API的权重,如上面示例中的@Produces批注所示。

如果您正在使用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

;

}

如果使用的是CXF,则可以使用此处指定的技术来构建新的序列化提供程序(在现有基础结构的基础上构建),以提供所需的特定格式的数据。声明其中的一些,每种都针对您想要的每种特定格式,并使用@Produces批注让机器为您处理其余的协商,尽管也可能也是支持标准JSON内容类型的想法这样普通客户就可以处理它,而无需特别关注您。这样,唯一真正的问题就是进行序列化的最佳方法是什么?我想您可以自己弄清楚……

[编辑]:在CXF文档中进行进一步的挖掘会发现,@Consumes和@Produces注释均被视为进行选择的轴。如果要使用两种方法来处理不同媒体类型的响应,则可以肯定。 (如果您使用的是自定义类型,则必须添加序列化和/或反序列化提供程序,但是您可以将大部分工作委派给标准提供程序。)我仍然要提醒您,应该在两种情况下仍要确保路径指示的资源应该相同;否则,它不是RESTful的。

多纳,谢谢您的回应。您已经指出了对序列化提供程序的提示,这是输出准备工作的一部分。更重要的问题是如何进行流程输入以及如何在同一路径下注册多个Resource类。

@Volodymyr:在同一路径下注册多个资源类?那肯定不能是RESTful的!重点是您公开了相同基础资源的许多视图。这些不同的JSON表示形式必须仅仅是查看一件事的不同方式。 (必须教序列化器如何构造不同的视图,但这就是您获得这种复杂性的目的。)

对于输入(即@Consumes批注),您只需要处理给出的内容即可。如果您将他们所发送的内容丢回他们的脸上,客户往往会讨厌它(当我有这样做的代码时,这给正在编写同伴客户库的同事带来极大的困惑……)

资源应该是一样的,那就是事实。我没有违反REST。在我的情况下,每个Resource类都旨在提供不同的表示形式。在这种情况下,版本控制与表示形式相关。但是新的表示形式(资源类)可以来自OSGi捆绑包。甚至类名也可能相同。这是一个非常动态的系统。我不想为此引入自己的框架,因为利用JAX-RS非常有用。

一种可能的解决方案是将一个@Path与

Content-Type:

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

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

问题是我不想在方法实现内部移动特定于版本的逻辑。我希望在外面处理。

无论如何,谢谢您的反馈:)

无论哪种方式,您都必须"创建一个图层以选择要调用的方法"。您提议此层的方式将基于@Consumes内容调用特定方法。我发布的解决方案中只有一个@Consumes并选择在其中调用的方法。对我而言,这是相同的,您使用批注处理并在方法上进行复制的一种方式。复制方法调用的另一种方法。

好吧,问题在于必须有多个具有相同@Path的资源类。所有这些类都应由不同版本的OSGi捆绑包提供,它们可能都处于活动状态。资源类不能是版本选择器逻辑的入口点。

我认为对不兼容的版本使用相同的URI(路径)基本上是一个坏主意;即使看起来更方便的做事方式。与其尝试解决这个问题,不如您可以重构事物以减少重复,而只是将入口方法视为简单的包装器,委托给共享功能。

@StaxMan不幸的是,委派共享功能的简单包装无法解决此问题

啊。我想我应该已经猜到,根据建议的解决方案,要解决的问题相当复杂。我希望您能以任何一种方式弄清楚。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值