Spring Boot验证通过ObjectMapper GET @RequestParam映射的JSON

时间:2018-02-20 00:41:44

标签: java spring rest spring-boot fasterxml

验证在Spring引导中传递给GET REST控制器的复杂JSON对象的最简单方法是什么?我使用 com.fasterxml.jackson.databind.ObjectMapper 进行映射?

这是控制器:

ld

我要验证的DTO请求对象:

@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上使用注释,但是当我这样做时,它们不会触发任何类型的异常,即 @NotNull 。我已尝试在控制器上使用 @Validated 以及 @RequestParam 中的 @Valid 的各种组合,并且没有任何因素导致验证触发。

2 个答案:

答案 0 :(得分:3)

在我看来,Hibernate Bean Validator可能是随时随地验证bean的annotated字段的最方便的方法之一。它就像setupforget

  • 设置Hibernate Bean Validator
  • 配置验证的完成方式
  • 在任何地方的bean上触发验证器

我按照here

给出的文档中的说明进行操作

设置依赖项

我使用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的自定义异常类。如您所见,我正在传递消息和violations列表,以防DTO有多个验证错误。

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课程
  • 我使用productId@NotNull
  • @Length(min=5, max=10)进行了注释
  • 我使用Postman发出GET请求,params的值为url-encoded json body

假设CustomBeanValidator在控制器中自动装配,在构造productRequest对象后触发验证,如下所示。

beanValidator.validateFields(productRequest);

如果使用基于注释的任何违规行为,上述内容将抛出异常。

异常控制器如何处理异常?

正如标题中所提到的,我使用ExceptionController来处理我的应用程序中的异常。

以下是exception handler ValidationsFatalException所在的@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()); } 的骨架,然后我更新消息并根据异常类型设置我想要的状态代码并返回自定义对象(即json你见下文)

params = {"productId":"abc123"}

测试1

原始parmas = %7B%22productId%22%3A%22abc123%22%7D
网址编码http://localhost:8080/app/product?params=%7B%22productId%22%3A%22abc123%22%7D
最终网址:params = {"productId":"ab"}
结果:一切都好。

测试2

原始parmas = %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实施,以提供import { REQUEST_HELLO_WORLD, RECEIVE_HELLO_WORLD } from "./actions/types"; function* helloWorld(action) { try { yield put({type: RECEIVE_HELLO_WORLD, text: "Hello world from redux saga!"}); } catch (e) { //Handling for error } } export default function* watchStartSaga () { yield takeLatest(REQUEST_HELLO_WORLD, helloWorld); } 错误消息的映射。

答案 1 :(得分:2)

你是说这样的意思吗?

@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;
    }
}