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



验证在春季启动中传递到GET REST控制器中的复杂JSON对象的最简单方法是什么,我正在使用com.fasterxml.jackson.databind.ObjectMapper映射?

这是控制器:

@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以及在@RequestParam中使用@Valid的各种组合,但没有任何东西导致验证触发。

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

  • 设置休眠 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的自定义异常类。如您所见,我正在传递一条消息和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类,如上所示
  • 我用@NotNull@Length(min=5, max=10)注释了productId
  • 我使用Postman发出GET请求,其params的值url-encodedjson 体

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

beanValidator.validateFields(productRequest);

如果基于使用的注释有任何冲突,上述将引发异常。

异常

控制器如何处理异常?

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

以下是我的exception handler骨架如何映射到ValidationsFatalException,然后我更新消息并根据异常类型设置我想要的状态代码并返回一个自定义对象(即您在下面看到的 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
结果:一切正常。

测试 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
结果:

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

相关内容

  • 没有找到相关文章

最新更新