原文: https://howtodoinjava.com/resteasy/jax-rs-resteasy-cache-control-with-etag-example/

ETags 或实体标签是很有用的 HTTP 标头,它们可以通过最小化系统上的服务器负载来帮助构建超快速的应用。 ETag 设置为对客户端的响应,因此客户端可以对条件请求使用各种控制请求标头,例如If-MatchIf-None-Matchjavax.ws.rs.core.Response.ResponseBuilder#tag()javax.ws.rs.core.EntityTag是用于 ETag 的实用类。

在服务器端,不变的 ETag(HTTP 请求附带的 ETag 与为请求的资源计算的 ETag 之间的匹配)只是意味着,资源与上次请求时间相比没有变化,因此只需发送 HTTP 304 标头[未修改]就足够了, 客户端可以使用本地可用的资源副本而不必担心。

为了演示该示例,我在服务类中有两个 REST API。

a)GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

此 API 将获取用户资源,该资源将附加有 ETag。 服务器将使用此 ETag 来验证用户详细信息是否已在上次请求中更新。

b)PUT http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

此 API 将对服务器上的用户资源进行一些更新,这将强制服务器再次返回新的资源副本。

让我们逐步构建示例。

步骤 1)使用 maven 创建一个新的 Java Web 项目并添加以下依赖项。

  1. <repositories>
  2. <repository>
  3. <id>jboss</id>
  4. <url>http://repository.jboss.org/maven2</url>
  5. </repository>
  6. </repositories>
  7. <dependencies>
  8. <!-- core library -->
  9. <dependency>
  10. <groupId>org.jboss.resteasy</groupId>
  11. <artifactId>resteasy-jaxrs</artifactId>
  12. <version>2.3.1.GA</version>
  13. </dependency>
  14. <!-- JAXB support -->
  15. <dependency>
  16. <groupId>org.jboss.resteasy</groupId>
  17. <artifactId>resteasy-jaxb-provider</artifactId>
  18. <version>2.3.1.GA</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>net.sf.scannotation</groupId>
  22. <artifactId>scannotation</artifactId>
  23. <version>1.0.2</version>
  24. </dependency>
  25. </dependencies>

步骤 2)制作一个类来表示用户资源

确保提供一些逻辑来验证修改此资源后的最后更新时间。 我添加了一个日期类型为lastModied的字段。

  1. package com.howtodoinjava.demo.rest.model;
  2. import java.io.Serializable;
  3. import java.util.Date;
  4. import javax.xml.bind.annotation.XmlAccessType;
  5. import javax.xml.bind.annotation.XmlAccessorType;
  6. import javax.xml.bind.annotation.XmlAttribute;
  7. import javax.xml.bind.annotation.XmlElement;
  8. import javax.xml.bind.annotation.XmlRootElement;
  9. @XmlAccessorType(XmlAccessType.NONE)
  10. @XmlRootElement(name = "user")
  11. public class User implements Serializable
  12. {
  13. @XmlAttribute(name = "id")
  14. private int id;
  15. @XmlAttribute(name="uri")
  16. private String uri;
  17. @XmlElement(name = "firstName")
  18. private String firstName;
  19. @XmlElement(name = "lastName")
  20. private String lastName;
  21. @XmlElement(name="last-modified")
  22. private Date lastModified;
  23. //Setters and Getters

步骤 3)在 DAO 层添加访问和修改资源的方法

我出于示例目的使用了静态HashMap。 在实时应用中,它将是一个数据库。 例如,我在映射中仅添加了一个用户。

  1. package com.howtodoinjava.demo.rest.data;
  2. import java.util.Date;
  3. import java.util.HashMap;
  4. import com.howtodoinjava.demo.rest.model.User;
  5. public class UserDatabase
  6. {
  7. public static HashMap<Integer, User> users = new HashMap<Integer, User>();
  8. static
  9. {
  10. User user = new User();
  11. user.setId(1);
  12. user.setFirstName("demo");
  13. user.setLastName("user");
  14. user.setUri("/user-management/users/1");
  15. user.setLastModified(new Date());
  16. users.put(1, user);
  17. }
  18. public static User getUserById(Integer id)
  19. {
  20. return users.get(id);
  21. }
  22. public static void updateUser(Integer id)
  23. {
  24. User user = users.get(id);
  25. user.setLastModified(new Date());
  26. }
  27. public static Date getLastModifiedById(Integer id)
  28. {
  29. return users.get(id).getLastModified();
  30. }
  31. }

步骤 4)编写 Restful API 以访问和修改资源

这是主要步骤。 我已经在上面描述了两个 API。 GET API 返回附加了 ETag 的用户资源。 此 ETag 是在用户资源的最后修改日期计算的。 这将确保每次用户更新时,都会生成一个新的 ETag。

  1. package com.howtodoinjava.demo.rest.service;
  2. import javax.ws.rs.GET;
  3. import javax.ws.rs.PUT;
  4. import javax.ws.rs.Path;
  5. import javax.ws.rs.PathParam;
  6. import javax.ws.rs.core.CacheControl;
  7. import javax.ws.rs.core.Context;
  8. import javax.ws.rs.core.EntityTag;
  9. import javax.ws.rs.core.Request;
  10. import javax.ws.rs.core.Response;
  11. import com.howtodoinjava.demo.rest.data.UserDatabase;
  12. @Path("/user-service")
  13. public class UserService
  14. {
  15. @GET
  16. @Path("/users/{id}")
  17. public Response getUserById(@PathParam("id") int id, @Context Request req)
  18. {
  19. //Create cache control header
  20. CacheControl cc = new CacheControl();
  21. //Set max age to one day
  22. cc.setMaxAge(86400);
  23. Response.ResponseBuilder rb = null;
  24. //Calculate the ETag on last modified date of user resource
  25. EntityTag etag = new EntityTag(UserDatabase.getLastModifiedById(id).hashCode()+"");
  26. //Verify if it matched with etag available in http request
  27. rb = req.evaluatePreconditions(etag);
  28. //If ETag matches the rb will be non-null;
  29. //Use the rb to return the response without any further processing
  30. if (rb != null)
  31. {
  32. return rb.cacheControl(cc).tag(etag).build();
  33. }
  34. //If rb is null then either it is first time request; or resource is modified
  35. //Get the updated representation and return with Etag attached to it
  36. rb = Response.ok(UserDatabase.getUserById(id)).cacheControl(cc).tag(etag);
  37. return rb.build();
  38. }
  39. @PUT
  40. @Path("/users/{id}")
  41. public Response updateUserById(@PathParam("id") int id)
  42. {
  43. //Update the User resource
  44. UserDatabase.updateUser(id);
  45. return Response.status(200).build();
  46. }
  47. }

警告:HTTP 标头中使用的日期的粒度不如数据源中使用的某些日期那么精确。 例如,数据库行中日期的精度可以定义为毫秒。 但是,HTTP 标头字段中的日期仅精确到秒。 在求值 HTTP 前提条件时,如果将java.util.Date对象直接与 HTTP 标头中的日期进行比较,则精度差异可能会产生意外结果。

为避免此问题,请使用日期对象的哈希码或使用某些规范化形式。

测试示例代码

1)首次请求:GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

使用 ETag 的 RESTEasy 缓存控制示例 - 图1

用户资源的第一次请求

2)后续请求:GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

使用 ETag 的 RESTEasy 缓存控制示例 - 图2

用户资源的后续请求

3)修改请求:PUT http://localhost:8080/RESTEasyEtagDemo/user-s:rvice/users/1

使用 ETag 的 RESTEasy 缓存控制示例 - 图3

使用 PUT API 更新的用户资源

4)更新资源:GET http://localhost:8080/RESTEasyEtagDemo/user-service/users/1

使用 ETag 的 RESTEasy 缓存控制示例 - 图4

检索更新的用户资源

要下载以上示例的源代码,请单击下面给出的链接。

祝您学习愉快!