1. 概述
當涉及到驗證用户輸入時,Spring Boot 提供了強大的支持,直接從“盒子裏”解決這一常見但至關重要的任務。
儘管 Spring Boot 支持與自定義驗證器無縫集成,但 執行驗證的默認標準是 Hibernate Validator,它是 Bean Validation 框架的參考實現。
在本教程中,我們將探討如何在 Spring Boot 中驗證領域對象。
2. Maven 依賴項
在本例中,我們將學習如何在 Spring Boot 中通過構建一個基本的 REST 控制器來驗證領域對象,具體方法是 Hibernate Validator。該控制器首先會接收一個領域對象,然後使用 Hibernate Validator對其進行驗證,最後將其持久化到內存中的 H2 數據庫。
該項目的依賴項相對標準:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
<scope>runtime</scope>
</dependency>如上所示,我們在 pom.xml文件中包含了 spring-boot-starter-web,因為我們需要它來創建 REST 控制器。 此外,我們還應該確保檢查 spring-boot-starter-jpa和在 Maven Central 上的最新版本 H2 數據庫。
從 Boot 2.3 版本開始,我們也需要顯式地添加 spring-boot-starter-validation依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.5.7</version>
</dependency>3. 一個簡單的領域類
在我們的項目依賴已建立的情況下,下一步我們需要定義一個示例 JPA 實體類,其作用僅限於建模用户。
下面是該類的定義:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@NotBlank(message = "Name is mandatory")
private String name;
@NotBlank(message = "Email is mandatory")
private String email;
// standard constructors / setters / getters / toString
}我們的 User 實體類的實現確實有些單薄,但它生動地展示瞭如何使用 Bean Validation 的約束來約束 name 和 email 字段。
為了簡化起見,我們僅使用 @NotBlank 約束來約束目標字段,並使用 message 屬性指定了錯誤消息。
因此,當 Spring Boot 驗證類實例時,約束字段 必須是非空且其截斷長度必須大於零。
此外,Bean Validation 提供了許多其他方便的約束,除了 @NotBlank 之外。這允許我們應用和組合不同的驗證規則到約束類中。有關更多信息,請參閲 官方 Bean Validation 文檔。
由於我們將使用 Spring Data JPA 將用户保存到內存中的 H2 數據庫,因此我們也需要定義一個簡單的 repository 接口,以便在 User 對象上具有基本的 CRUD 功能:
@Repository
public interface UserRepository extends CrudRepository<User, Long> {}4. 實現 REST 控制器
當然,我們需要實現一個層,以便獲取我們 User 對象受限字段的值。
因此,我們可以根據驗證結果進行驗證並執行一些進一步的任務。
Spring Boot 通過實現 REST 控制器,使得這個看似複雜的過程變得非常簡單。
讓我們來看一下 REST 控制器的實現:
@RestController
public class UserController {
@PostMapping("/users")
ResponseEntity<String> addUser(@Valid @RequestBody User user) {
// persisting the user
return ResponseEntity.ok("User is valid");
}
// standard constructors / other methods
}
在 Spring REST 環境中,addUser() 方法的實現相當標準。
當然,最相關的部分是使用 @Valid 註解。
當 Spring Boot 發現一個帶有 @Valid 註解的參數時,它會自動啓動默認的 JSR 380 實現——Hibernate Validator 並驗證該參數。當目標參數未通過驗證時,Spring Boot 會拋出 MethodArgumentNotValidException 異常。
5. <em @ExceptionHandler</em>> 註解
雖然 Spring Boot 自動驗證傳遞到 addUser() 方法的 User 對象非常方便,但這個過程缺少一個關鍵部分,即如何處理驗證結果。
因此,我們可以將其用於處理驗證錯誤:
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}我們指定了 MethodArgumentNotValidException 異常作為需要處理的異常。因此,Spring Boot會在指定的 User 對象無效時調用該方法。
該方法將每個無效字段的名稱和後置驗證錯誤消息存儲在一個 Map 中。然後,它將 Map 作為 JSON 格式返回給客户端,以便進行進一步處理。
簡單來説,REST 控制器允許我們輕鬆處理不同端點的請求,驗證 User 對象,並將響應發送為 JSON 格式。
該設計足夠靈活,可以處理通過多個 Web 層(包括模板引擎如 Thymeleaf 以及功能齊全的 JavaScript 框架如 Angular)的控制器響應。
6. 測試 REST 控制器
我們可以輕鬆地使用集成測試來測試我們的 REST 控制器的功能。
讓我們首先進行模擬/自動注入 <em >UserRepository</em> 接口實現,以及 <em >UserController</em> 實例,以及 <em >MockMvc</em> 對象:
@RunWith(SpringRunner.class)
@WebMvcTest
@AutoConfigureMockMvc
public class UserControllerIntegrationTest {
@MockBean
private UserRepository userRepository;
@Autowired
UserController userController;
@Autowired
private MockMvc mockMvc;
//...
}
由於我們僅測試 Web 層,因此使用 @WebMvcTest 註解。它允許我們使用 Spring Test 提供的靜態方法集輕鬆測試請求和響應。同時,它還利用了 MockMvcRequestBuilders 和 MockMvcResultMatchers 類。
現在,讓我們使用有效的和無效的 User 對象在請求體中,測試 addUser() 方法:
@Test
public void whenPostRequestToUsersAndValidUser_thenCorrectResponse() throws Exception {
MediaType textPlainUtf8 = new MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8"));
String user = "{\"name\": \"bob\", \"email\" : \"[email protected]\"}";
mockMvc.perform(MockMvcRequestBuilders.post("/users")
.content(user)
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content()
.contentType(textPlainUtf8));
}
@Test
public void whenPostRequestToUsersAndInValidUser_thenCorrectResponse() throws Exception {
String user = "{\"name\": \"\", \"email\" : \"[email protected]\"}";
mockMvc.perform(MockMvcRequestBuilders.post("/users")
.content(user)
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isBadRequest())
.andExpect(MockMvcResultMatchers.jsonPath("$.name", Is.is("Name is mandatory")))
.andExpect(MockMvcResultMatchers.content()
.contentType(MediaType.APPLICATION_JSON_UTF8));
}
}
此外,我們還可以使用免費的 API 生命週期測試應用程序(例如 Postman)測試 REST 控制器 API。
7. 運行示例應用程序
最後,我們可以使用標準 main() 方法運行我們的示例項目:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner run(UserRepository userRepository) throws Exception {
return (String[] args) -> {
User user1 = new User("Bob", "[email protected]");
User user2 = new User("Jenny", "[email protected]");
userRepository.save(user1);
userRepository.save(user2);
userRepository.findAll().forEach(System.out::println);
};
}
}
正如預期的那樣,我們應該在控制枱中看到幾個 User 對象被打印出來。
向 http://localhost:8080/users 端點發送一個有效的 User 對象,將會返回字符串 “User is valid”。
同樣,向帶有缺少 name 和 email 值的 User 對象發送 POST 請求,將會返回以下響應:
{
"name":"Name is mandatory",
"email":"Email is mandatory"
}8. 結論
在本文中,我們學習了在 Spring Boot 中進行驗證的基礎知識。