带标题的条件操作
本节展示了 Spring Data REST 如何利用标准 HTTP 头来提升性能、条件化操作,并助力构建更复杂的前端。
ETag
、If-Match
和 If-None-Match
头部
ETag 头信息 提供了一种标记资源的方式。这可以防止客户端相互覆盖,同时也能够减少不必要的调用。
考虑以下示例:
示例 1. 带有版本号的 POJO
class Sample {
@Version Long version; 1
Sample(Long version) {
this.version = version;
}
}
@Version
注解(如果你在使用 Spring Data JPA,则是指 JPA 中的注解;对于其他模块,则是指 Spring Data 的org.springframework.data.annotation.Version
注解)将该字段标记为版本标记。
在前面的示例中,当 Spring Data REST 将 POJO 作为 REST 资源提供时,它会带有一个 ETag
标头,该标头的值是版本字段的值。
如果提供了 If-Match
头部,我们可以有条件地 PUT
、PATCH
或 DELETE
该资源,例如以下所示:
curl -v -X PATCH -H 'If-Match: <value of previous ETag>' ...
只有当资源的当前 ETag
状态与 If-Match
头匹配时,操作才会执行。这一机制防止了客户端之间的冲突。两个不同的客户端可以获取资源并拥有相同的 ETag
。如果其中一个客户端更新了资源,它将在响应中获得一个新的 ETag
。但第一个客户端仍然持有旧的头信息。如果该客户端尝试使用 If-Match
头进行更新,更新将失败,因为它们的 ETag
不再匹配。相反,该客户端将收到一个 HTTP 412 Precondition Failed
消息。然后,客户端可以根据需要进行同步。
术语“版本”在不同的数据存储中可能具有不同的语义,甚至在你的应用程序内部也可能有不同的含义。Spring Data REST 有效地委托给数据存储的元模型来判断某个字段是否被版本化。如果是,那么只有在 ETag
元素匹配的情况下才允许列出的更新。
If-None-Match 头 提供了一种替代方案。与条件更新不同,If-None-Match
允许进行条件查询。考虑以下示例:
curl -v -H 'If-None-Match: <value of previous etag>' ...
前面的命令(默认情况下)执行一个 GET
请求。Spring Data REST 在执行 GET
请求时会检查 If-None-Match
头信息。如果该头信息与 ETag 匹配,它会认为资源没有发生变化,因此不会发送资源的副本,而是返回一个 HTTP 304 Not Modified
状态码。从语义上讲,它的意思是“如果提供的头信息值与服务器端版本不匹配,则发送整个资源。否则,不要发送任何内容。”
这个 POJO 来自一个基于 ETag
的单元测试,因此它没有 @Entity
(JPA)或 @Document
(MongoDB)注解,这在应用代码中是预期的。它仅关注带有 @Version
注解的字段如何生成 ETag
头。
If-Modified-Since
请求头
If-Modified-Since 请求头 提供了一种检查资源自上次请求以来是否已更新的方法,这可以让应用程序避免重新发送相同的数据。考虑以下示例:
示例 2. 域类型中捕获的最后修改日期
@Document
public class Receipt {
public @Id String id;
public @Version Long version;
public @LastModifiedDate Date date; 1
public String saleItem;
public BigDecimal amount;
}
Spring Data Commons 的
@LastModifiedDate
注解允许以多种格式捕获此信息(JodaTime 的DateTime
、遗留的 JavaDate
和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
标头,用于更新客户端。如果资源未更改,您将收到 HTTP 304 Not Modified
状态码。
标题已经完美格式化,以便在未来的查询中返回。
不要将不同查询的 header 值混用。结果可能会非常糟糕。只有在请求完全相同的 URI 和参数时才能使用这些 header 值。
构建更高效的前端架构
ETag
元素结合 If-Match
和 If-None-Match
头信息,可以帮助你构建一个对用户数据计划和移动设备电池寿命更加友好的前端。具体操作如下:
-
识别需要锁定的实体,并添加一个版本属性。
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
状态码。
通过在 DOM 中(或在原生移动应用的其他位置)嵌入 ETag
元素和 Last-Modified
值,您可以减少数据消耗和电池电量消耗,避免重复获取相同内容。您还可以避免与其他客户端冲突,并在需要协调差异时收到提醒。
通过这种方式,只需在前端进行一些微调并进行实体级别的编辑,后端就可以提供时效性强的详细信息,这些信息在构建客户友好的客户端时可以派上用场。