基於頭部的條件操作

本節將展示 Spring Data REST 如何使用標準的 HTTP 頭部來提升效能、執行條件操作,並有助於構建更精巧的前端。

ETagIf-MatchIf-None-Match 頭部

The ETag header 提供了一種標記資源的方式。這可以防止客戶端相互覆蓋,同時也可以減少不必要的呼叫。

請看以下示例

示例 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 頭部,其值為版本欄位的值。

如果我們提供一個如下所示的 If-Match 頭部,我們可以有條件地對該資源執行 PUTPATCHDELETE 操作

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 提供了一種檢查資源自上次請求以來是否已更新的方式,這讓應用程式可以避免重複傳送相同的資料。請考慮以下示例

示例 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 頭部

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-MatchIf-None-Match header 結合使用,可以讓您構建一個對使用者的流量套餐和手機電池壽命更友好的前端。要實現這一點

  1. 識別需要鎖定的實體並新增版本屬性。

    HTML5 很好地支援 data-* 屬性,因此可以將版本儲存在 DOM 中(例如在 data-etag 屬性中)。

  2. 識別哪些條目透過跟蹤最新更新可以受益。在獲取這些資源時,將 Last-Modified 值儲存在 DOM 中(例如儲存在 data-last-modified 中)。

  3. 在獲取資源時,也在您的 DOM 節點中嵌入 self URI(例如 data-uridata-self),這樣可以方便地返回到該資源。

  4. 調整 PUT/PATCH/DELETE 操作以使用 If-Match,並處理 HTTP 412 Precondition Failed 狀態碼。

  5. 調整 GET 操作以使用 If-None-MatchIf-Modified-Since,並處理 HTTP 304 Not Modified 狀態碼。

透過將 ETag 元素和 Last-Modified 值嵌入到您的 DOM 中(或者對於原生移動應用,可能嵌入到其他地方),您可以透過不反覆檢索相同的內容來減少資料和電池電量的消耗。您還可以避免與其他客戶端衝突,並在需要協調差異時收到警報。

透過這種方式,只需對前端進行少量調整並進行一些實體層面的修改,後端就能提供時間敏感的詳細資訊,您可以在構建使用者友好的客戶端時利用這些資訊。