此版本仍在开发中,尚未被视为稳定版本。如需最新稳定版本,请使用 Spring Data REST 5.0.4spring-doc.cadn.net.cn

基于请求头的条件操作

本节展示了 Spring Data REST 如何使用标准 HTTP 头部来提升性能、实现条件化操作,并助力构建更 sophisticated 的前端。spring-doc.cadn.net.cn

ETag, If-Match,和If-None-Matchheaders

The ETag 标头提供了一种标记资源的方法。这可以防止客户端相互覆盖,同时也有助于减少不必要的调用。spring-doc.cadn.net.cn

考虑以下示例:spring-doc.cadn.net.cn

示例 1. 一个带有版本号的 POJO
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 头,其值为版本字段的值。spring-doc.cadn.net.cn

如果我们提供一个 PUT 请求头(例如如下所示),就可以有条件地对该资源执行 PATCHDELETEIf-Match 操作:spring-doc.cadn.net.cn

curl -v -X PATCH -H 'If-Match: <value of previous ETag>' ...

仅当资源当前的 ETag 状态与 If-Match 请求头匹配时,才会执行该操作。这一保护机制可防止多个客户端相互覆盖彼此的更改。两个不同的客户端可以获取同一资源,并拥有相同的 ETag。如果其中一个客户端更新了该资源,响应中会返回一个新的 ETag。但第一个客户端仍持有旧的 If-Match。如果该客户端尝试使用 412 Precondition Failed 头进行更新,由于两者不再匹配,更新将失败。此时,服务器会返回一个 HTTP 6(前置条件失败)响应。客户端随后可根据需要采取相应措施进行同步。spring-doc.cadn.net.cn

术语“版本”在不同的数据存储中可能具有不同的语义,甚至在同一应用程序内部也可能具有不同的含义。Spring Data REST 实际上会委托给数据存储的元模型,以判断某个字段是否为版本化字段;如果是,则仅在 ETag 元素匹配的情况下才允许所列出的更新操作。

If-None-Match 标头提供了一种替代方案。与条件更新不同,If-None-Match支持条件查询。请考虑以下示例:spring-doc.cadn.net.cn

curl -v -H 'If-None-Match: <value of previous etag>' ...

上述命令(默认情况下)执行一个 GET 请求。Spring Data REST 在执行 If-None-Match 请求时会检查 GET 请求头。如果该请求头的值与资源的 ETag 匹配,它就会判定资源未发生变化,并不会返回资源的副本,而是返回 HTTP 304 Not Modified 状态码。从语义上讲,它的含义是:“如果提供的请求头值与服务器端版本不匹配,则发送完整的资源;否则,不发送任何内容。”spring-doc.cadn.net.cn

该 POJO 来自一个基于 ETag 的单元测试,因此不像应用程序代码中那样包含 @Entity(JPA)或 @Document(MongoDB)注解。它仅关注带有 @Version 注解的字段如何生成 ETag 响应头。

If-Modified-Sinceheader

The If-Modified-Since 标头提供了一种检查资源自上次请求以来是否已更新的方法,这使得应用程序可以避免重新发送相同的数据。请考虑以下示例:spring-doc.cadn.net.cn

示例 2. 在领域类型中捕获的最后修改日期
@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 DateCalendar、JDK8 的日期/时间类型,以及 long/Long)。

在前面的示例中,对于日期字段,Spring Data REST 会返回一个类似于以下内容的 Last-Modified 响应头:spring-doc.cadn.net.cn

Last-Modified: Wed, 24 Jun 2015 20:28:15 GMT

该值可以被捕获并用于后续查询,以避免在数据未更新的情况下重复获取相同的数据,如下例所示:spring-doc.cadn.net.cn

curl -H "If-Modified-Since: Wed, 24 Jun 2015 20:28:15 GMT" ...

通过上述命令,您请求仅在指定时间之后资源发生更改时才获取该资源。如果资源已更改,您将收到更新后的 Last-Modified 响应头,可用于更新客户端。如果未更改,则会收到 HTTP 304 Not Modified 状态码。spring-doc.cadn.net.cn

该头部已完美格式化,可直接用于未来的查询请求中。spring-doc.cadn.net.cn

不要将请求头值与不同的查询混合使用。否则可能导致严重后果。仅在您请求完全相同的 URI 和参数时,才使用这些请求头值。

构建更高效的前端

ETag 元素结合 If-MatchIf-None-Match 请求头,可帮助您构建对用户数据流量和移动设备电池寿命更加友好的前端。具体做法如下:spring-doc.cadn.net.cn

  1. 识别需要加锁的实体,并添加一个版本属性。spring-doc.cadn.net.cn

    HTML5 很好地支持 data-* 属性,因此可以将版本信息存储在 DOM 中(例如存储在 data-etag 属性中)。spring-doc.cadn.net.cn

  2. 识别出那些需要跟踪最近更新的条目。在获取这些资源时,将 Last-Modified 值存储在 DOM 中(例如使用 data-last-modified 属性)。spring-doc.cadn.net.cn

  3. 在获取资源时,也请在您的 DOM 节点中嵌入 self URI(例如使用 data-uridata-self 属性),以便轻松返回到该资源。spring-doc.cadn.net.cn

  4. 调整 PUT/PATCH/DELETE 操作以使用 If-Match,并处理 HTTP 412 Precondition Failed 状态码。spring-doc.cadn.net.cn

  5. 调整 GET 操作,以使用 If-None-MatchIf-Modified-Since,并处理 HTTP 304 Not Modified 状态码。spring-doc.cadn.net.cn

通过在您的 DOM 中(或者对于原生移动应用,可能在其他位置)嵌入 ETag 元素和 Last-Modified 值,您可以避免反复获取相同的内容,从而减少数据流量和电池消耗。您还可以避免与其他客户端发生冲突,并在需要协调差异时及时收到提醒。spring-doc.cadn.net.cn

通过这种方式,只需对前端稍作调整,并进行一些实体级别的修改,后端就能提供与时效相关的信息,让你在构建用户友好的客户端时加以利用。spring-doc.cadn.net.cn