如何在 JAX-RS 中管理状态?

How to manage state in JAX-RS?

如何配置 JAX-RS 2 实现 (RESTEasy 3) 以将应用程序的状态发送到客户端?

在 JSF 中,我可以使用 STATE_SAVING_METHOD 参数来完成。

有使用 JAX-RS 的标准方法吗?

<context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
</context-param>

只是一个例子来说明我的问题,我想将 JAX-RS 提供程序配置为 return 客户端的 cart 变量状态,这样我就不会保留内存中的数据或保留会话状态。

@Path("/cart")
public class ShoppingCart {

    // List of products presented to the user
    private List<Product> availableProducts;

    // Products selected by the user
    private List<Product> cart;

    @GET
    public List<Product> addProduct() {
        return availableProducts;
    }

    @POST
    public void addProduct(Product product) {
        cart.add(product);
    }

}

更新

我想添加支持管理应用程序状态的想法的参考。我同意理论上,所有服务都应该是无状态的,但在实践中我发现,如果可以维护状态,很多场景会更有意义,特别是出于安全和生产力的原因。

似乎该模式通常是通过超链接完成的,内容经过适当加密以保护数据和完整性。我现在正在研究支持这些场景的 JAX-RS 工具。

How to Manage Application State 来自 RESTful Web 服务指南:

Since HTTP is a stateless protocol, each request is independent of any previous request. However, interactive applications often require clients to follow a sequence of steps in a particular order. This forces servers to temporarily store each client’s current position in those sequences outside the protocol. The trick is to manage state such that you strike a balance between reliability, network performance, and scalability.

也来自 JAX-RS documentation:

Stateful interactions through hyperlinks: Every interaction with a resource is stateless; that is, request messages are self-contained. Stateful interactions are based on the concept of explicit state transfer. Several techniques exist to exchange state, such as URI rewriting, cookies, and hidden form fields. State can be embedded in response messages to point to valid future states of the interaction. See Using Entity Providers to Map HTTP Response and Request Entity Bodies and “Building URIs” in the JAX-RS Overview document for more information.

由于您通常会在 RESTful 应用程序中避免服务器端状态,因此您根本无法将状态发送到客户端。你没有。通常您通过 URI 标识您的资源并从例如数据库中获取它们。

在伪代码中你可以这样实现它:

@Path("{customerId}/cart")
public class ShoppingCart {

    @GET
    public List<Product> getProducts(@PathParam("customerId") long customerId) {
        // check if the caller has access-rights
        // fetch all products for given customerID from a database, 
        // and return it to the client
    }

    @POST
    public Response addProduct(@PathParam("customerId") long customerId, Product product) {
        // check if the caller has access-rights,
        // save the product in the database
        // and return a 201 maybe with the product as entity
    }

}

虽然 lefloh provided a very clear answer,但我想添加更多细节以帮助您理解 REST 架构。

REST 架构风格

REST 代表 Representational State T转账。此架构独立于协议,但经常通过 HTTP 协议实现。

Roy Thomas Fielding 的博士论文 chapter 5 中定义了 REST 架构风格。并将以下一组约束添加到此体系结构样式中:

通过应用上面定义的约束,引入了某些架构属性,例如可见性、可移植性、可靠性、可扩展性和网络效率。

无状态约束

在 REST 应用程序中,从客户端到服务器的每个请求都必须包含服务器可以理解的所有必要信息。有了它,您不依赖于存储在服务器上的任何 session 上下文,也不会破坏 stateless constraint:

5.1.3 Stateless

[...] communication must be stateless in nature [...], such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client. [...]

例如,当访问需要身份验证的受保护资源时,每个请求都必须包含所有必要的数据才能正确 authenticated/authorized。这意味着将对每个请求执行 身份验证

JAX-RS生命周期

使用 JAX-RS 创建 REST 应用程序时,您有 资源 classes,即 Java class使用 JAX-RS 注释实现相应 Web 资源的 es。

资源 classes 是 POJO,至少有一个用 @Path or a request method designator (@DELETE, @GET, @HEAD, @OPTIONS, @POST, @PUT, custom annotations created with @HttpMethod 注释的方法。

根据 JAX-RS specification,资源 class 遵循明确定义的生命周期:

3.1.1 Lifecycle and Environment

By default a new resource class instance is created for each request to that resource. First the constructor is called, then any requested dependencies are injected , then the appropriate method is invoked and finally the object is made available for garbage collection. [...]

在 JAX-RS 中,没有这样的 javax.faces.STATE_SAVING_METHOD 或类似的。没有状态。没有session.

您的资源 class 可以遵循 lefloh's answer 中建议的 pseudo-code。并且不要忘记你总是可以对你的资源进行注入 class:

@Path("/cart")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public class ShoppingCart {

    @Inject
    private ShoppingCartService service;

    @GET
    public Response getProducts() {
        ...
    }

    @POST
    public Response addProduct(Product product) {
        ...
    }
}

JAX-RS

中的超链接

REST 的一个非常重要的方面是 超链接 ,URI,客户端可以使用它们将 Web 服务转换为新的应用程序状态。这被称为 超媒体作为应用程序状态的引擎 ,通常缩写为 HATEOAS.

JAX-RS 2.0 引入了 Link class, which serves as a representation of the web link defined in RFC 5988. The JAX-RS Link class 添加 API 支持在 HTTP 消息中提供额外的元数据。

A Link 可以作为附加的 HTTP header 序列化到 HTTP 响应(可能提供多个 Link header,因此可以提供多个链接在一条消息中)。这样的 HTTP header 可能看起来像:

Link: <http://example.com/api/books/1/chapter/2>; rel="prev"; title="previous chapter"

使用 JAX-RS API 生成 Links 可以按如下方式完成:

Response response = Response.ok()
                      .link("http://example.com/api/books/1/chapter/2", "prev")
                      .build();

Link 的实例也可以通过调用 Link API 上的工厂方法之一直接创建 returns a Link.Builder 可用于配置和生成新链接:

URI uri = URI.create("http://example.com/api/books/1/chapter/2");
Link link = Link.fromUri(uri).rel("prev").title("previous chapter").build();

Response response = Response.ok().link(link).build();

Links 也可以添加到您的响应实体模型中:

public class Book {

    private String title;
    
    private Link link;
    
    // Default constructor, getters and setters ommited
}

例如,当序列化为 JSON 时,您将得到如下内容:

{
    "title" : "The Lord of the Rings",
    "link" : {
        "rel" : "self",
        "uri" : "http://example.com/api/books/1",
        "title": "self"
      }
}