1. 概述
本文檔介紹 Spring Data REST 驗證器的基本介紹。如果您需要先回顧 Spring Data REST 的基本概念,請務必訪問本文檔以鞏固基礎知識。
簡單來説,通過 Spring Data REST,我們可以通過 REST API 輕鬆地向數據庫添加新的條目,但我們當然也需要確保在實際持久化之前數據有效。
本文檔延續了之前的文章,我們將重用我們之前設置好的項目。
如果您正在尋找如何開始使用 Spring Data REST – 這是一個快速入門的好方法:
2. 使用驗證器
從 Spring 3 開始,框架引入了 Validator 接口,可用於驗證對象。
2.1. 動機
在上一篇文章中,我們定義了實體具有兩個屬性——name 和 email。
並且,為了創建新的資源,我們只需要運行:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "name" : "Test", "email" : "[email protected]" }'
http://localhost:8080/users這個 POST 請求會將提供的 JSON 對象保存到我們的數據庫中,操作完成後將返回:
{
"name" : "Test",
"email" : "[email protected]",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/1"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/1"
}
}
}由於我們提供了有效數據,因此預計結果將是積極的。但是,如果刪除 name 屬性,或者將值設置為空字符串 String ,會發生什麼情況?
為了測試第一個場景,我們將運行之前修改過的命令,其中將空字符串設置為 name 屬性的值:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users使用該命令,我們將會得到以下響應:
{
"name" : "",
"email" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/1"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/1"
}
}
}對於第二個場景,我們將從請求中移除 屬性:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "email" : "Baggins" }' http://localhost:8080/users對於該命令,我們將會得到以下響應:
{
"name" : null,
"email" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/users/2"
},
"websiteUser" : {
"href" : "http://localhost:8080/users/2"
}
}
}如我們所見,這兩個請求都已成功,我們可以確認其狀態碼為 201,以及指向我們對象的 API 鏈接。
這種行為不可接受,因為我們希望避免將部分數據插入到數據庫中。
2.2. Spring Data REST 事件
在每次對 Spring Data REST API 的調用過程中,Spring Data REST 導出器會生成各種事件,列出如下:
- BeforeCreateEvent
- AfterCreateEvent
- BeforeSaveEvent
- AfterSaveEvent
- BeforeLinkSaveEvent
- AfterLinkSaveEvent
- BeforeDeleteEvent
- AfterDeleteEvent
由於所有事件的處理方式類似,我們只展示如何處理 beforeCreateEvent,該事件在將新對象保存到數據庫之前生成。
2.3. 定義驗證器 (Validator)
為了創建我們自己的驗證器,我們需要實現 org.springframework.validation.Validator 接口,並提供 supports 和 validate 方法。
supports 方法用於檢查驗證器是否支持提供的請求,而 validate 方法用於驗證請求中的數據。
讓我們定義一個 WebsiteUserValidator 類:
public class WebsiteUserValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return WebsiteUser.class.equals(clazz);
}
@Override
public void validate(Object obj, Errors errors) {
WebsiteUser user = (WebsiteUser) obj;
if (checkInputString(user.getName())) {
errors.rejectValue("name", "name.empty");
}
if (checkInputString(user.getEmail())) {
errors.rejectValue("email", "email.empty");
}
}
private boolean checkInputString(String input) {
return (input == null || input.trim().length() == 0);
}
}Errors 對象是一個特殊類,旨在包含 validate 方法提供的所有錯誤。稍後我們將演示如何使用 Errors 對象中提供的消息。
要添加新的錯誤消息,我們必須調用 errors.rejectValue(nameOfField, errorMessage)。
在定義驗證器之後,我們需要將其映射到在請求接受後生成的特定事件。
例如,在我們的案例中,beforeCreateEvent 在我們想要將新對象插入到數據庫中時生成。但是,由於我們想要在請求中驗證對象,因此我們需要首先定義我們的驗證器。
這可以通過三種方式完成:
- 添加帶有名稱 "beforeCreateWebsiteUserValidator" 的 Component 註解。 Spring Boot 將識別前綴 beforeCreate,這決定了我們想要捕獲的事件,並且它還將識別 WebsiteUser 類來自 Component 名稱。
@Component("beforeCreateWebsiteUserValidator")
public class WebsiteUserValidator implements Validator {
...
}@Bean
public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
return new WebsiteUserValidator();
}@SpringBootApplication
public class SpringDataRestApplication implements RepositoryRestConfigurer {
public static void main(String[] args) {
SpringApplication.run(SpringDataRestApplication.class, args);
}
@Override
public void configureValidatingRepositoryEventListener(
ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", new WebsiteUserValidator());
}
}<ul>
<li>對於本例,<em >WebsiteUserValidator</em> 類無需任何註釋。</li>
</ul>
2.4. 事件發現 Bug
目前,Spring Data REST 中存在一個 Bug – 它影響了事件的發現。
如果調用生成 beforeCreate 事件的 POST 請求,我們的應用程序將不會調用驗證器,因為該事件不會被發現,這是由於該 Bug 造成的。
針對此問題,一個簡單的解決方法是將其所有事件插入到 Spring Data REST 的 ValidatingRepositoryEventListener 類中:
@Configuration
public class ValidatorEventRegister implements InitializingBean {
@Autowired
ValidatingRepositoryEventListener validatingRepositoryEventListener;
@Autowired
private Map<String, Validator> validators;
@Override
public void afterPropertiesSet() throws Exception {
List<String> events = Arrays.asList("beforeCreate");
for (Map.Entry<String, Validator> entry : validators.entrySet()) {
events.stream()
.filter(p -> entry.getKey().startsWith(p))
.findFirst()
.ifPresent(
p -> validatingRepositoryEventListener
.addValidator(p, entry.getValue()));
}
}
}3. 測試
在 第 2.1 節 中,我們展示了,在缺少驗證器的情況下,我們可以將不包含名稱屬性的對象添加到數據庫中,這並不是我們希望的行為,因為我們沒有檢查數據完整性。
如果我們想要添加相同對象但沒有 屬性,並且使用了提供的驗證器,將會得到以下錯誤:
curl -i -X POST -H "Content-Type:application/json" -d
'{ "email" : "[email protected]" }' http://localhost:8080/users{
"timestamp":1472510818701,
"status":406,
"error":"Not Acceptable",
"exception":"org.springframework.data.rest.core.
RepositoryConstraintViolationException",
"message":"Validation failed",
"path":"/users"
}正如我們所見,來自請求的缺失數據已被檢測到,對象未被保存到數據庫中。我們的請求返回了 500 HTTP 狀態碼和內部錯誤消息。
錯誤消息中並未提及請求中的問題。為了使其更具信息量,我們需要修改響應對象。
在《Spring 異常處理》一文中,我們展示瞭如何處理框架生成的異常,所以這絕對是一個值得一讀的資料。
由於我們的應用程序生成了 RepositoryConstraintViolationException 異常,我們將創建一個處理程序來修改響應消息。
這是我們的 RestResponseEntityExceptionHandle 類:
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
ResponseEntityExceptionHandler {
@ExceptionHandler({ RepositoryConstraintViolationException.class })
public ResponseEntity<Object> handleAccessDeniedException(
Exception ex, WebRequest request) {
RepositoryConstraintViolationException nevEx =
(RepositoryConstraintViolationException) ex;
String errors = nevEx.getErrors().getAllErrors().stream()
.map(p -> p.toString()).collect(Collectors.joining("\n"));
return new ResponseEntity<Object>(errors, new HttpHeaders(),
HttpStatus.PARTIAL_CONTENT);
}
}使用此自定義處理程序,我們的返回對象將包含有關所有檢測到的錯誤的詳細信息。
4. 結論
在本文中,我們展示了驗證器對於任何 Spring Data REST API 來説都是必不可少的,它為數據插入提供了一層額外的安全保障。
我們還説明了如何輕鬆創建新的驗證器,並使用註解。