|
此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Data REST 5.0.4! |
基于请求头的条件操作
本节展示了 Spring Data REST 如何使用标准 HTTP 头部来提升性能、实现条件化操作,并助力构建更 sophisticated 的前端。
ETag, If-Match,和If-None-Matchheaders
The ETag 标头提供了一种标记资源的方法。这可以防止客户端相互覆盖,同时也有助于减少不必要的调用。
考虑以下示例:
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 头,其值为版本字段的值。
如果我们提供一个 PUT 请求头(例如如下所示),就可以有条件地对该资源执行 PATCH、DELETE 或 If-Match 操作:
curl -v -X PATCH -H 'If-Match: <value of previous ETag>' ...
仅当资源当前的 ETag 状态与 If-Match 请求头匹配时,才会执行该操作。这一保护机制可防止多个客户端相互覆盖彼此的更改。两个不同的客户端可以获取同一资源,并拥有相同的 ETag。如果其中一个客户端更新了该资源,响应中会返回一个新的 ETag。但第一个客户端仍持有旧的 If-Match。如果该客户端尝试使用 412 Precondition Failed 头进行更新,由于两者不再匹配,更新将失败。此时,服务器会返回一个 HTTP 6(前置条件失败)响应。客户端随后可根据需要采取相应措施进行同步。
术语“版本”在不同的数据存储中可能具有不同的语义,甚至在同一应用程序内部也可能具有不同的含义。Spring Data REST 实际上会委托给数据存储的元模型,以判断某个字段是否为版本化字段;如果是,则仅在 ETag 元素匹配的情况下才允许所列出的更新操作。 |
If-None-Match 标头提供了一种替代方案。与条件更新不同,If-None-Match支持条件查询。请考虑以下示例:
curl -v -H 'If-None-Match: <value of previous etag>' ...
上述命令(默认情况下)执行一个 GET 请求。Spring Data REST 在执行 If-None-Match 请求时会检查 GET 请求头。如果该请求头的值与资源的 ETag 匹配,它就会判定资源未发生变化,并不会返回资源的副本,而是返回 HTTP 304 Not Modified 状态码。从语义上讲,它的含义是:“如果提供的请求头值与服务器端版本不匹配,则发送完整的资源;否则,不发送任何内容。”
该 POJO 来自一个基于 ETag 的单元测试,因此不像应用程序代码中那样包含 @Entity(JPA)或 @Document(MongoDB)注解。它仅关注带有 @Version 注解的字段如何生成 ETag 响应头。 |
If-Modified-Sinceheader
The If-Modified-Since 标头提供了一种检查资源自上次请求以来是否已更新的方法,这使得应用程序可以避免重新发送相同的数据。请考虑以下示例:
@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 响应头,可用于更新客户端。如果未更改,则会收到 HTTP 304 Not Modified 状态码。
该头部已完美格式化,可直接用于未来的查询请求中。
| 不要将请求头值与不同的查询混合使用。否则可能导致严重后果。仅在您请求完全相同的 URI 和参数时,才使用这些请求头值。 |
构建更高效的前端
ETag 元素结合 If-Match 和 If-None-Match 请求头,可帮助您构建对用户数据流量和移动设备电池寿命更加友好的前端。具体做法如下:
-
识别需要加锁的实体,并添加一个版本属性。
HTML5 很好地支持
data-*属性,因此可以将版本信息存储在 DOM 中(例如存储在data-etag属性中)。 -
识别出那些需要跟踪最近更新的条目。在获取这些资源时,将
Last-Modified值存储在 DOM 中(例如使用data-last-modified属性)。 -
在获取资源时,也请在您的 DOM 节点中嵌入
selfURI(例如使用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 值,您可以避免反复获取相同的内容,从而减少数据流量和电池消耗。您还可以避免与其他客户端发生冲突,并在需要协调差异时及时收到提醒。
通过这种方式,只需对前端稍作调整,并进行一些实体级别的修改,后端就能提供与时效相关的信息,让你在构建用户友好的客户端时加以利用。