基于头部的条件操作
本节将展示 Spring Data REST 如何使用标准的 HTTP 头部来提升性能、执行条件操作,并有助于构建更精巧的前端。
ETag
、If-Match
和 If-None-Match
头部
The ETag
header 提供了一种标记资源的方式。这可以防止客户端相互覆盖,同时也可以减少不必要的调用。
请看以下示例
class Sample {
@Version Long version; (1)
Sample(Long version) {
this.version = version;
}
}
1 | @Version 注解(如果你正在使用 Spring Data JPA,则是 JPA 的那个;对于所有其他模块,则是 Spring Data 的 org.springframework.data.annotation.Version 注解)将此字段标记为版本标识。 |
上述示例中的 POJO,当 Spring Data REST 将其作为 REST 资源提供时,会有一个 ETag
头部,其值为版本字段的值。
如果我们提供一个如下所示的 If-Match
头部,我们可以有条件地对该资源执行 PUT
、PATCH
或 DELETE
操作
curl -v -X PATCH -H 'If-Match: <value of previous ETag>' ...
只有当资源的当前 ETag
状态与 If-Match
header 匹配时,操作才会被执行。这种保障措施可以防止客户端相互覆盖。两个不同的客户端可以获取资源并拥有相同的 ETag
。如果一个客户端更新了资源,它会在响应中获得一个新的 ETag
。但第一个客户端仍然持有旧的 header。如果该客户端尝试使用 If-Match
header 进行更新,更新会失败,因为它们不再匹配。相反,该客户端会收到一个 HTTP 412 Precondition Failed
消息。客户端随后可以根据需要进行追赶。
术语“版本”在不同的数据存储中可能具有不同的语义,甚至在您的应用程序中也可能具有不同的语义。Spring Data REST 有效地委托给数据存储的元模型来判断一个字段是否已版本化,如果是,则只有当 ETag 元素匹配时,才允许执行列出的更新。 |
The If-None-Match
header 提供了一种替代方案。与条件更新不同,If-None-Match
允许进行条件查询。请考虑以下示例
curl -v -H 'If-None-Match: <value of previous etag>' ...
前面的命令(默认情况下)运行一个 GET
。Spring Data REST 在执行 GET
时会检查 If-None-Match
header。如果 header 与 ETag
匹配,它会断定资源没有改变,并且不发送资源的副本,而是返回一个 HTTP 304 Not Modified
状态码。从语义上讲,它的意思是“如果提供的 header 值与服务器端的版本不匹配,则发送整个资源。否则,不发送任何东西。”
这个 POJO 来自一个基于 ETag 的单元测试,因此它不包含应用代码中通常会有的 @Entity (JPA) 或 @Document (MongoDB) 注解。它仅关注带有 @Version 的字段如何生成 ETag 头部。 |
If-Modified-Since
头部
The If-Modified-Since
header 提供了一种检查资源自上次请求以来是否已更新的方式,这让应用程序可以避免重复发送相同的数据。请考虑以下示例
@Document
public class Receipt {
public @Id String id;
public @Version Long version;
public @LastModifiedDate Date date; (1)
public String saleItem;
public BigDecimal amount;
}
1 | Spring Data Commons 的 @LastModifiedDate 注解允许以多种格式(JodaTime 的 DateTime 、传统的 Java Date 和 Calendar 、JDK8 日期/时间类型以及 long /Long )捕获此信息。 |
有了上述示例中的日期字段,Spring Data REST 将返回一个类似于如下所示的 Last-Modified
头部
Last-Modified: Wed, 24 Jun 2015 20:28:15 GMT
可以捕获此值并用于后续查询,以避免在数据未更新时重复获取相同的数据,如下例所示
curl -H "If-Modified-Since: Wed, 24 Jun 2015 20:28:15 GMT" ...
使用前面的命令,您正在请求只在资源自指定时间以来发生变化时才获取它。如果发生了变化,您会获得一个更新的 Last-Modified
header,用以更新客户端。如果没有变化,您会收到一个 HTTP 304 Not Modified
状态码。
该头部已完美格式化,可用于未来的查询。
请勿在不同的查询中混用头部值。结果可能会是灾难性的。仅在请求完全相同的 URI 和参数时使用这些头部值。 |
构建更高效的前端架构
ETag
元素与 If-Match
和 If-None-Match
header 结合使用,可以让您构建一个对用户的流量套餐和手机电池寿命更友好的前端。要实现这一点
-
识别需要锁定的实体并添加版本属性。
HTML5 很好地支持
data-*
属性,因此可以将版本存储在 DOM 中(例如在data-etag
属性中)。 -
识别哪些条目通过跟踪最新更新可以受益。在获取这些资源时,将
Last-Modified
值存储在 DOM 中(例如存储在data-last-modified
中)。 -
在获取资源时,也在您的 DOM 节点中嵌入
self
URI(例如data-uri
或data-self
),这样可以方便地返回到该资源。 -
调整
PUT
/PATCH
/DELETE
操作以使用If-Match
,并处理 HTTP412 Precondition Failed
状态码。 -
调整
GET
操作以使用If-None-Match
和If-Modified-Since
,并处理 HTTP304 Not Modified
状态码。
通过将 ETag
元素和 Last-Modified
值嵌入到您的 DOM 中(或者对于原生移动应用,可能嵌入到其他地方),您可以通过不反复检索相同的内容来减少数据和电池电量的消耗。您还可以避免与其他客户端冲突,并在需要协调差异时收到警报。
通过这种方式,只需对前端进行少量调整并进行一些实体层面的修改,后端就能提供时间敏感的详细信息,您可以在构建用户友好的客户端时利用这些信息。