Spring 引导验证 JSON 通过 ObjectMapper GET @RequestParam 映射
Spring Boot Validate JSON Mapped via ObjectMapper GET @RequestParam
验证复杂 JSON 对象被传递到 spring 引导中的 GET REST 控制器的最简单方法是什么,我正在使用 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[=27 中使用 @Valid 的各种组合=] 并且没有任何事情导致验证触发。
在我看来,Hibernate Bean Validator
可能是随时随地验证bean的annotated
字段最方便的方法之一。就像 setup
和 forget
- 设置 Hibernate Bean 验证器
- 配置验证的方式
- 在任何地方触发 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);
}
}
}
异常class
我上面使用的ValidationsFatalException
是一个扩展RuntimeException
的自定义异常class。如您所见,我正在传递一条消息和一个 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
class如上图
- 我用
@NotNull
和 @Length(min=5, max=10)
注释了 productId
- 我使用
Postman
发出 GET
请求,其中 params
的值为 url-encoded
json body
假设CustomBeanValidator
在controller中自动装配,在构建productRequest
object后触发验证如下。
beanValidator.validateFields(productRequest);
如果有任何基于使用的注释的违规行为,以上将抛出异常。
异常控制器如何处理异常?
如标题所述,我使用ExceptionController
来处理我的应用程序中的异常。
这是 exception handler
的框架,ValidationsFatalException
映射到该框架,然后我更新消息并根据异常类型和 return 自定义设置我想要的状态代码object(即您在下面看到的 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"}
Url 编码 parmas = %7B%22productId%22%3A%22abc123%22%7D
最终 URL:http://localhost:8080/app/product?params=%7B%22productId%22%3A%22abc123%22%7D
结果:一切顺利。
测试 2
原始 params = {"productId":"ab"}
Url 编码 parmas = %7B%22productId%22%3A%22ab%22%7D
最终 URL: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;
}
}
验证复杂 JSON 对象被传递到 spring 引导中的 GET REST 控制器的最简单方法是什么,我正在使用 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[=27 中使用 @Valid 的各种组合=] 并且没有任何事情导致验证触发。
在我看来,Hibernate Bean Validator
可能是随时随地验证bean的annotated
字段最方便的方法之一。就像 setup
和 forget
- 设置 Hibernate Bean 验证器
- 配置验证的方式
- 在任何地方触发 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);
}
}
}
异常class
我上面使用的ValidationsFatalException
是一个扩展RuntimeException
的自定义异常class。如您所见,我正在传递一条消息和一个 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
class如上图 - 我用
@NotNull
和@Length(min=5, max=10)
注释了 - 我使用
Postman
发出GET
请求,其中params
的值为url-encoded
json body
productId
假设CustomBeanValidator
在controller中自动装配,在构建productRequest
object后触发验证如下。
beanValidator.validateFields(productRequest);
如果有任何基于使用的注释的违规行为,以上将抛出异常。
异常控制器如何处理异常?
如标题所述,我使用ExceptionController
来处理我的应用程序中的异常。
这是 exception handler
的框架,ValidationsFatalException
映射到该框架,然后我更新消息并根据异常类型和 return 自定义设置我想要的状态代码object(即您在下面看到的 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"}
Url 编码 parmas = %7B%22productId%22%3A%22abc123%22%7D
最终 URL:http://localhost:8080/app/product?params=%7B%22productId%22%3A%22abc123%22%7D
结果:一切顺利。
测试 2
原始 params = {"productId":"ab"}
Url 编码 parmas = %7B%22productId%22%3A%22ab%22%7D
最终 URL: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;
}
}