Java中@Valid子物件註釋

banq發表於2024-06-12

在本教程中,我們將瞭解如何使用@Valid註釋來驗證物件及其巢狀的子物件。

當傳入資料是基本資料型別(例如整數或字串)時,驗證傳入資料可能很簡單。但是,當傳入資訊是物件(特別是物件圖)時,驗證就比較困難了。幸運的是,@Valid註釋簡化了巢狀子物件的驗證。

什麼是@Valid註解?
@Valid註釋來自Jakarta Bean Validation規範,標記需要驗證的特定引數。

使用此註解可確保傳遞給方法或儲存在欄位中的資料符合指定的驗證規則。這有助於我們提高資料完整性和一致性。

當在JavaBean的欄位或方法上使用時,它會觸發所有定義的約束檢查。Bean Validation API中最常用的一些約束包括@NotNull、@NotBlank、@NotEmpty、@Size、@Email、@Pattern 等。

如何在子物件上使用@Valid註解
首先,我們必須確定驗證規則,並將前面提到的驗證約束應用到欄位。

接下來,我們定義一個代表專案的類,它包含一個巢狀的User物件,我們將用@Valid註釋來裝飾它:

public class User {
    @NotBlank(message = <font>"User name must be present")
    @Size(min = 3, max = 50, message =
"User name size not valid")
    private String name;
    @NotBlank(message =
"User email must be present")
    @Email(message =
"User email format is incorrect")
    private String email;
   
// omitted constructors, getters and setters<i>
}
public class Project {
    @NotBlank(message =
"Project title must be present")
    @Size(min = 3, max = 20, message =
"Project title size not valid")
    private String title;
    @Valid
    private User owner;
   
// omitted constructors, getters and setters<i>
}

之後,我們將使用Validator例項上的validate()方法執行驗證。讓我們確保子物件已透過測試驗證:

@Test
public void whenInvalidProjectAndUser_thenAssertConstraintViolations() {
    Project project = new Project(null);
    project.setOwner(new User(null, <font>"invalid-email"));
    List<String> messages = validate(project);
    assertEquals(3, messages.size());
    assertTrue(messages.contains(
"Project title must be present"));
    assertTrue(messages.contains(
"User name must be present"));
    assertTrue(messages.contains(
"User email format is incorrect"));
}
private List<String> validate(Project project) {
    return validator.validate(project)
      .stream()
      .map(ConstraintViolation::getMessage)
      .collect(Collectors.toList());
}

此外,使用@Valid註釋的 bean 驗證可以與Spring和Jakarta EE等框架完美配合。透過在控制器類的方法引數上使用註釋,我們甚至可以在進入控制器方法之前執行驗證,這對於保持資料一致性非常有用。

瞭解物件圖驗證
現在我們已經瞭解瞭如何使用@Valid,讓我們更好地理解它為什麼以這種方式工作。在物件有其他巢狀物件的情況下,我們必須應用一種稱為物件圖驗證的機制。

此機制可驗證物件圖中相關物件的完整結構。所有使用@Valid註釋的子物件(及其子物件)在其父物件為 時都會進行驗證。換句話說,驗證會在整個圖中遞回應用。

經過此圖的遍歷,我們得到了ConstraintViolations集合,其中包含來自巢狀物件的所有組合驗證違規。

由於我們以遞迴方式驗證圖中的每個物件,因此我們可能會遇到迴圈引用問題,即物件以迴圈方式相互引用。這可能會使我們陷入無限迴圈,不斷重複驗證相同的物件。

幸運的是,Jakarta Bean Validation 包含定義驗證路徑的概念,該路徑被描述為從根物件開始的@Valid關聯序列。實現會跟蹤當前路徑中已驗證的每個例項,從根物件開始。如果同一個例項在給定的導航路徑中出現多次,驗證例程將忽略它,從而防止無限迴圈。

子物件上的註解使用
現在我們已經瞭解瞭如何使用@Valid註釋以及它在底層的工作原理,讓我們來看看所有可以使用它的地方。我們將研究如何在容器物件中的巢狀例項、集合和型別引數上使用@Valid。

1. 使用@Valid驗證巢狀例項
驗證巢狀例項的一種方法是使用欄位訪問策略,這與我們在上一個示例中驗證專案內巢狀的User物件的方法相同。簡單地說,我們用@Valid註釋修飾欄位,然後將該例項新增到導航路徑中:

@Valid
private User owner;

類似地,驗證巢狀例項的另一種方法是使用屬性訪問策略,這意味著我們可以將@Valid放在 getter 方法上,以此方式訪問屬性的狀態:

@Valid
public User getOwner() {
    return owner;
}

2. 使用@Valid驗證可迭代物件
集合、陣列或java.lang.Iterebale介面的任何其他實現都符合@Valid註釋的條件。如果我們在這種情況下進行註釋,我們將按照相同的規則對Iterable的每個元素應用驗證。

重要的是要知道,如果集合是java.util.Map介面的實現,則只會驗證值。我們必須專門註釋鍵以觸發對它們的驗證。

例如,讓我們檢查一個同時驗證鍵和值的Map :

private Map<@Valid User, @Valid Task> assignedTasks;

3. 在容器物件和型別引數上使用註釋
在容器物件和型別引數上應用註解非常相似,讓我們首先看一下如何操作:

@Valid 
private List<Task> tasks;
    
private List<@Valid Task> tasks;

第一個例子展示了在容器上使用註解,而第二個例子則直接在型別引數上使用註解。在這種情況下,沒有區別,它們都按我們預期的方式工作。一般來說,我們應該避免在兩個地方使用它,因為這可能會導致容器元素被驗證兩次。

我們可以看到,在這些情況下使用註釋是靈活的,但它們並不總是按照我們想要的方式工作。在我們有巢狀通用容器的情況下,為了驗證容器的內容,我們必須在內部容器的型別引用上應用註釋。

讓我們看一個List巢狀在Map中的例子:

private Map<String, List<@Valid Task>> taskByType;


結論
在本文中,我們瞭解了什麼是@Valid註釋,如何使用它對子物件執行驗證,以及物件圖驗證如何工作。

@Valid註釋是一個功能強大的工具,我們可以在不同的地方使用它來確保事物按預期進行驗證。它很棒,因為它會自動檢查圖中的每個已驗證物件,使我們的工作更輕鬆。

 

相關文章