簡體   English   中英

Spring Boot驗證通過ObjectMapper GET @RequestParam映射的JSON

[英]Spring Boot Validate JSON Mapped via ObjectMapper GET @RequestParam

驗證在我使用com.fasterxml.jackson.databind.ObjectMapper映射的春季啟動中傳遞到GET REST控制器中的復雜JSON對象的最簡單方法是什么?

這是控制器:

@RestController
@RequestMapping("/products")
public class ProductsController {

@GetMapping
public ProductResponse getProducts(
        @RequestParam(value = "params") String requestItem
) throws IOException {
    final ProductRequest productRequest =
            new ObjectMapper()
                    .readValue(requestItem, ProductRequest.class);

    return productRetriever.getProductEarliestAvailabilities(productRequest);
}}

我要驗證的DTO請求對象:

public class ProductRequest {
private String productId;

public String getProductId() {
    return productId;
}

public void setProductId(String productId) {
    this.productId = productId;
}}

我當時在考慮在請求DTO上使用注釋,但是當我這樣做時,它們不會觸發任何類型的異常,即@NotNull 我試着在控制器使用@Validated以及@Valid@RequestParam並沒有什么導致了驗證觸發的各種組合。

以我的觀點, Hibernate Bean Validator可能是隨時隨地驗證Bean的帶annotated字段的最便捷方法之一。 就像setupforget

  • 設置Hibernate Bean驗證器
  • 配置驗證方式
  • 在任何地方觸發Bean上的驗證器

我按照此處給出的文檔中的說明進行操作

設置依賴

我使用Gradle,所以我將添加所需的依賴項,如下所示

// Hibernate Bean validator
compile('org.hibernate:hibernate-validator:5.2.4.Final')

創建一個通用的bean valdiator

我按照文檔中的描述設置了一個bean驗證器接口,然后使用它來驗證所有帶注釋的東西

public interface CustomBeanValidator {
    /**
     * Validate all annotated fields of a DTO object and collect all the validation and then throw them all at once.  
     * 
     * @param object
     */
    public <T> void validateFields(T object); 
}

實現上述接口,如下所示

@Component
public class CustomBeanValidatorImpl implements CustomBeanValidator {
    ValidatorFactory valdiatorFactory = null; 

    public CustomBeanValidatorImpl() {
        valdiatorFactory = Validation.buildDefaultValidatorFactory(); 
    }

    @Override
    public <T> void validateFields(T object) throws ValidationsFatalException {
        Validator validator = valdiatorFactory.getValidator(); 
        Set<ConstraintViolation<T>> failedValidations = validator.validate(object); 

        if (!failedValidations.isEmpty()) {
            List<String> allErrors = failedValidations.stream().map(failure -> failure.getMessage())
                    .collect(Collectors.toList());
            throw new ValidationsFatalException("Validation failure; Invalid request.", allErrors);
        }
    }
}

異常類

我上面使用的ValidationsFatalException是擴展RuntimeException的自定義異常類。 如您所見,如果DTO有多個驗證錯誤,我將傳遞一條消息和一個violations列表。

public class ValidationsFatalException extends RuntimeException {
    private String message; 
    private Throwable cause;
    private List<String> details; 

    public ValidationsFatalException(String message, Throwable cause) {
        super(message, cause);
    } 

    public ValidationsFatalException(String message, Throwable cause, List<String> details) {
        super(message, cause); 
        this.details = details;
    }

    public List<String> getDetails() {
        return details; 
    }
}

模擬場景

為了測試這是否有效,我從字面上使用您的代碼進行測試,這就是我所做的

  • 如上所示創建一個端點
  • 自動CustomBeanValidator並觸發它的validateFields方法,將productRequest傳遞給它,如下所示
  • 如上所示創建一個ProductRequest
  • 我用@NotNull@Length(min=5, max=10)注釋了productId
  • 我用Postman發出的GET請求的paramsurl-encoded json主體

假設CustomBeanValidator在控制器中是自動CustomBeanValidator的,那么在構造productRequest對象之后,觸發驗證如下。

beanValidator.validateFields(productRequest);

如果基於使用的注釋有任何違規,以上內容將引發異常。

異常控制器如何處理異常?

如標題中所述,我使用ExceptionController來處理應用程序中的異常。

這是ValidationsFatalException映射到我的exception handler的框架的方式,然后我更新消息並根據異常類型設置所需的狀態代碼並返回自定義對象(即,您在下面看到的json)

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({SomeOtherException.class, ValidationsFatalException.class})
public @ResponseBody Object handleBadRequestExpection(HttpServletRequest req, Exception ex) {
    if(ex instanceof CustomBadRequestException) 
        return new CustomResponse(400, HttpStatus.BAD_REQUEST, ex.getMessage()); 
    else 
        return new DetailedCustomResponse(400, HttpStatus.BAD_REQUEST, ex.getMessage(),((ValidationsFatalException) ex).getDetails()); 
}

測試1

原始params = {"productId":"abc123"}
網址編碼的parmas = %7B%22productId%22%3A%22abc123%22%7D
最終http://localhost:8080/app/product?params=%7B%22productId%22%3A%22abc123%22%7D網址: http://localhost:8080/app/product?params=%7B%22productId%22%3A%22abc123%22%7D
結果:一切都很好。

測試2

原始params = {"productId":"ab"}
網址編碼的parmas = %7B%22productId%22%3A%22ab%22%7D
最終http://localhost:8080/app/product?params=%7B%22productId%22%3A%22ab%22%7D網址: http://localhost:8080/app/product?params=%7B%22productId%22%3A%22ab%22%7D
結果:

{
    "statusCode": 400,
    "status": "BAD_REQUEST",
    "message": "Validation failure; Invalid request.",
    "details": [
        "length must be between 5 and 10"
    ]
}

您可以擴展Validator實現以提供field vs message錯誤消息的映射。

你的意思是這樣嗎?

@RequestMapping("/products")
public ResponseEntity getProducts(
        @RequestParam(value = "params") String requestItem) throws IOException {

    ProductRequest request = new ObjectMapper().
            readValue(requestItem, ProductRequest.class);

    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

    Validator validator = factory.getValidator();
    Set<ConstraintViolation<ProductRequest>> violations
            = validator.validate(request);

    if (!violations.isEmpty()) {
        return ResponseEntity.badRequest().build();
    }
    return ResponseEntity.ok().build();
}



public class ProductRequest {
    @NotNull
    @Size(min = 3)
    private String id;

    public String getId() {
        return id;
    }

public String setId( String id) {
    return this.id = id;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM