Spring Data REST 驗證器指南

REST,Spring Data
Remote
0
02:59 PM · Dec 01 ,2025

1. 概述

本文檔介紹 Spring Data REST 驗證器的基本介紹。如果您需要先回顧 Spring Data REST 的基本知識,請務必訪問本文檔以鞏固基礎知識。

簡單來説,通過 Spring Data REST,我們可以通過 REST API 簡單地向數據庫添加新的條目,但我們當然也需要確保數據在實際持久化之前是有效的。

本文檔延續了之前的文章,我們將重用我們在那裏設置的現有項目。

 

如果您正在尋找如何首先 開始使用 Spring Data REST – 這裏提供了一種快速上手的方法:

2. Using Validators

從 Spring 3 開始,框架包含 Validator 接口 – 可以用於驗證對象。

2.1. 動機

在上一篇文章中,我們定義了實體有兩個屬性 – nameemail

因此,為了創建一個新的資源,我們只需要運行:

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"
    }
  }
}

對於第二個場景,我們將從請求中刪除屬性 name:

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 接口,並使用 supportsvalidate 方法。

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 在新的對象保存到數據庫之前生成。但是,由於我們想要驗證請求中的對象,因此首先需要定義我們的驗證器。

這可以通過三種方式實現:

  • 添加 Component 註解,名稱為“beforeCreateWebsiteUserValidator”。 Spring Boot 將識別前綴 beforeCreate,該前綴確定我們要捕獲的事件,並且還將識別 WebsiteUser 類來自 Component 名稱。
    @Component("beforeCreateWebsiteUserValidator")
    public class WebsiteUserValidator implements Validator {
        ...
    }
  • 創建一個 Bean 在 Application Context 中,帶有 @Bean 註解:
    @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(p -> p.getKey().startsWith("beforeCreate"), new WebsiteUserValidator());
        }
    }
    • 對於此案例,您不需要在 WebsiteUserValidator 類上添加任何註釋。

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 節 中,我們展示了,如果沒有驗證器,我們可以將沒有名稱屬性的對象添加到我們的數據庫中,這並不是我們期望的行為,因為我們不檢查數據完整性。

如果我們想添加相同對象,但沒有 name 屬性,並且提供了驗證器,我們會得到以下錯誤:

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 的重要性,它為數據插入提供了一層額外的安全保障。

我們還説明瞭如何輕鬆創建新的驗證器,使用註解。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.