官术网_书友最值得收藏!

  • Spring實戰(第6版)
  • (美)克雷格·沃斯
  • 2498字
  • 2022-12-20 19:14:51

在設計新的taco作品的時候,如果用戶沒有選擇配料或者沒有為他們的作品指定名稱,將會怎樣?當提交表單的時候,如果沒有填寫所需的地址輸入域,又將發生什么?或者,他們在信用卡域中輸入了一個根本不合法的數字,又該怎么辦?

就目前的情況來看,沒有什么能夠阻止用戶在創建taco的時候不選擇任何配料,或者輸入空的快遞地址,甚至將他們最喜歡的歌詞作為信用卡號提交。這是因為我們還沒有指明這些輸入域該如何進行校驗。

有種表單校驗方法就是在 processTaco()和processOrder()方法中添加大量亂七八糟的if/then代碼塊,逐個檢查每個輸入域,以確保它們滿足對應的校驗規則。但是,這樣操作會非常煩瑣,并且會使代碼難以閱讀和調試。

比較幸運的是,Spring支持JavaBean校驗API(JavaBean Validation API,也稱為JSR-303),使我們能夠更容易地聲明檢驗規則,而不必在應用程序代碼中顯式編寫聲明邏輯。

要在Spring MVC中應用校驗,我們需要:

在構建文件中添加Spring Validation starter;

在要被校驗的類上聲明校驗規則,具體到我們的場景中,要被校驗的類就是Taco類;

在需要進行校驗的控制器方法中聲明要進行校驗,具體來講,此處的控制器方法也就是DesignTacoController的processTaco()方法和OrderController的processOrder()方法;

修改表單視圖以展現校驗錯誤。

Validation API提供了一些注解,可以添加到領域對象的屬性上,以便聲明校驗規則。Hibernate的Validation AP實現又添加了一些校驗注解。通過將Spring Validation starter添加到構建文件中,我們就能將這兩者引入項目中。在Spring Boot Starter向導的I/O區域下面選中Validation復選框就可以實現這一點,但是如果想手動編寫構建文件,在Maven pom.xml中添加如下的條目同樣可以做到這一點:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

如果你使用Gradle,需要如下的依賴:

implementation 'org.springframework.boot:spring-boot-starter-validation'

我們是否還需要validation starter?

在早期版本的Spring Boot中,Spring Validation starter會自動包含到web starter中。從Spring Boot 2.3.0版本開始,如果想要使用校驗,需要顯式地將其添加到構建文件中。

validation starter已經準備就緒,我們看一下如何使用其中的一些注解來校驗用戶提交的Taco和TacoOrder。

對于Taco類來說,我們想要確保name屬性不能為空或null,同時希望有至少一項配料被選中。程序清單2.11展示了更新后的Taco類,它使用@NotNull和@Size注解來聲明這些校驗規則。

程序清單2.11 為Taco領域類添加校驗

package tacos;
import java.util.List;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;

@Data
public class Taco {

  @NotNull
  @Size(min = 5, message = "Name must be at least 5 characters long")
  private String name;
  @NotNull
  @Size(min = 1, message = "You must choose at least 1 ingredient")
  private List<Ingredient> ingredients;

}

我們可以發現,除了要求name屬性不為null之外,我們還聲明它的值的長度至少為5個字符。

在對提交的taco訂單進行校驗時,必須要給TacoOrder類添加注解。對于地址相關的屬性,我們只想確保用戶沒有提交空白字段。為此,我們可以使用@NotBlank注解。

但是,支付相關的字段就比較復雜了。我們不僅要確保ccNumber屬性不為空,還要保證它所包含的值是一個合法的信用卡號碼。ccExpiration屬性必須符合MM/YY格式(兩位的月份和兩位的年份),ccCVV屬性需要是3位數字。為了實現這種校驗,我們需要其他的一些JavaBean Validation API注解,并結合來自Hibernate Validator的注解。程序清單2.12展現了校驗TacoOrder類所需的變更。

程序清單2.12 校驗訂單的字段

package tacos;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.CreditCardNumber;
import java.util.List;
import java.util.ArrayList;
import lombok.Data;

@Data
public class TacoOrder {

  @NotBlank(message = "Delivery name is required")
  private String deliveryName;

  @NotBlank(message = "Street is required")
  private String deliveryStreet;

  @NotBlank(message = "City is required")
  private String deliveryCity;

  @NotBlank(message = "State is required")
  private String deliveryState;

  @NotBlank(message = "Zip code is required")
  private String deliveryZip;

  @CreditCardNumber(message = "Not a valid credit card number")
  private String ccNumber;

  @Pattern(regexp = "^(0[1-9]|1[0-2])([\\/])([2-9][0-9])$",
           message = "Must be formatted MM/YY")
  private String ccExpiration;

  @Digits(integer = 3, fraction = 0, message = "Invalid CVV")
  private String ccCVV;

  private List<Taco> tacos = new ArrayList<>();

  public void addTaco(Taco taco) {
    this.tacos.add(taco);
  }
}

我們可以看到,ccNumber屬性添加了@CreditCardNumber注解。這個注解聲明該屬性的值必須是合法的信用卡號,它要能通過Luhn算法的檢查。這能防止用戶有意或無意地輸入錯誤的數據,但并不能確保這個信用卡號真的分配給了某個賬戶,也不能保證這個賬號能夠用來進行支付。

令人遺憾的是,目前還沒有現成的注解來校驗ccExpiration屬性的MM/YY格式。在這里,我使用了@Pattern注解并為其提供了一個正則表達式,確保屬性值符合預期的格式。如果你想知道如何解釋這個正則表達式,我建議你參考一些在線的正則表達式指南,比如Regular Expressions Info網站。正則表達式仿佛一種魔法,已經超出了本書的范圍。最后,ccCVV屬性上添加了@Digits注解,確保它的值包含3位數字。

所有的校驗注解都包含了一個message屬性,該屬性定義了當輸入的信息不滿足聲明的校驗規則時,要給用戶展現的消息。

現在,我們已經聲明了如何校驗Taco和TacoOrder,接下來要重新修改每個控制器,讓表單在POST提交至對應的控制器方法時,執行對應的校驗。

要校驗提交的Taco,我們需要為DesignTacoController中processTaco()方法的Taco參數添加一個JavaBean Validation API的@Valid注解,如程序清單2.13所示。

程序清單2.13 校驗POST提交的Taco

import javax.validation.Valid;
import org.springframework.validation.Errors;

...

  @PostMapping
  public String processTaco(
          @Valid Taco taco, Errors errors,
          @ModelAttribute TacoOrder tacoOrder) {

    if (errors.hasErrors()) {
      return "design";
    }

    tacoOrder.addTaco(taco);
    log.info("Processing taco: {}", taco);

    return "redirect:/orders/current";
  }

@Valid注解會告訴Spring MVC要對提交的Taco對象進行校驗,而校驗時機是在它綁定完表單數據之后、調用processDesign()之前。如果存在校驗錯誤,這些錯誤的細節將會捕獲到一個Errors對象中并傳遞給processTaco()。processTaco ()方法的前幾行會查閱Errors對象,調用其hasErrors()方法判斷是否有校驗錯誤。如果存在校驗錯誤,這個方法將不會處理Taco對象并返回“design”視圖名,以使表單重新展現。

為了對提交的TacoOrder對象進行校驗,OrderController的processOrder()方法也需要進行類似的變更,如程序清單2.14所示。

程序清單2.14 校驗POST提交的TacoOrder

@PostMapping
public String processOrder(@Valid TacoOrder order, Errors errors,
        SessionStatus sessionStatus) {
  if (errors.hasErrors()) {
    return "orderForm";
  }

  log.info("Order submitted: {}", order);
  sessionStatus.setComplete();

  return "redirect:/";
}

在這兩個場景中,如果沒有校驗錯誤,方法都允許處理提交的數據;如果存在校驗錯誤,請求將會被轉發至表單視圖上,以便用戶糾正他們的錯誤。

但是,用戶該如何知道有哪些要糾正的錯誤呢?如果我們無法指出表單上的錯誤,那么用戶只能不斷猜測如何才能成功提交表單。

Thymeleaf提供了便捷訪問Errors對象的方法,這就是借助fields及其th:errors屬性。舉例來說,為了展現信用卡字段的校驗錯誤,我們可以添加一個<span>元素,該元素會將對校驗錯誤的引用用到訂單的表單模板上,如程序清單2.15所示。

程序清單2.15 展現校驗錯誤

<label for = "ccNumber">Credit Card #: </label>
      <input type = "text" th:field = "*{ccNumber}"/>
      <span class = "validationError"
            th:if = "${#fields.hasErrors('ccNumber')}"
            th:errors = "*{ccNumber}">CC Num Error</span>

在這里,<span>元素使用class屬性來為錯誤添加樣式,以引起用戶的注意。除此之外,它還使用th:if屬性來決定是否要顯示該元素。fields屬性的hasErrors()方法會檢查ccNumber域是否存在錯誤。如果存在,將會渲染<span>。

th:errors屬性引用了ccNumber輸入域,如果該輸入域存在錯誤,它會將<span>元素的占位符內容替換為校驗信息。

在為訂單表單的其他輸入域都添加類似的<span>標簽之后,如果提交錯誤信息,表單會如圖2.5所示。其中,錯誤信息提示姓名、城市和郵政編碼字段為空,而且所有支付相關的輸入域均未滿足校驗條件。

2-5

圖2.5 在訂單表單上展現校驗錯誤

現在,我們的Taco Cloud控制器不僅能夠展現和捕獲輸入,還能校驗用戶提交的信息是否滿足一定的基本驗證規則。接下來,我們后退一步,重新考慮第1章中的HomeController,并學習一種替代實現方案。

主站蜘蛛池模板: 沅陵县| 宁南县| 桐柏县| 东乡| 阳江市| 韩城市| 平潭县| 江川县| 阜平县| 台江县| 谢通门县| 常德市| 墨竹工卡县| 右玉县| 大宁县| 探索| 锡林浩特市| 慈利县| 徐汇区| 宣汉县| 大关县| 乐至县| 涞水县| 普格县| 聂拉木县| 汝南县| 白山市| 朔州市| 五莲县| 望都县| 白山市| 同仁县| 天祝| 体育| 柳州市| 罗山县| 洪湖市| 黎平县| 遵化市| 龙山县| 孟村|