路由器函数中的 REST 请求参数验证
REST request argument Validation In Router Function
在 Spring 控制器方法中,我们可以使用 @Valid
和类似这样的东西
进行 REST 请求参数验证
@PostMapping(REGISTER)
public ResponseEntity<SomeData> registerSomeData(@RequestBody @Valid final SomeData someData) {
...................
}
public class SomeData {
@Size(min = 2, max = 20)
private String firstname;
@Digits(fraction = 0, integer = 10)
private Integer customerID;
@NotNull
private Customer customer;
}
如果请求不符合这些约束,那么 Spring 框架将抛出 Bad Request Exception(400)。
有了Spring5个路由器函数,我不明白我们怎么能做到这一点,因为我们不能在路由器函数中给出@Valid
。
您不能将基于注释的验证与(功能)Spring Webflux 一起使用。参见 。
如果您绝对需要基于注释的验证,您应该知道您可以继续使用传统的 Spring MVC 和 Spring 5(或非功能性 Webflux)。
有点恼火的是,这个有用的功能似乎并没有被应用到函数世界中,但是自己实现验证步骤确实并不难。方法如下。
创建一个 bean 来执行验证:
@Component
public class RequestValidator {
@Inject
Validator validator;
public <T> Mono<T> validate(T obj) {
if (obj == null) {
return Mono.error(new IllegalArgumentException());
}
Set<ConstraintViolation<T>> violations = this.validator.validate(obj);
if (violations == null || violations.isEmpty()) {
return Mono.just(obj);
}
return Mono.error(new ConstraintViolationException(violations));
}
}
现在在您的处理程序函数中包含一个执行验证的步骤。在此示例中,FindRequest
class 是一个 JSON 域模型 class,其中包含验证注释,例如 @NotEmpty
和 @NotNull
等。调整您的方式根据这个调用反应式数据存储库的虚构示例构建 ServerResponse
。
@Component
public class MyHandler {
@Inject
RequestValidator validator;
public Mono<ServerResponse> findAllPeople(ServerRequest request) {
return request.bodyToMono(FindRequest.class)
.flatMap(this.validator::validate)
.flatMap(fr -> ServerResponse
.ok()
.body(this.repo.findAllByName(fr.getName()), Person.class));
}
}
可以使用相同的方法来扩展功能以处理 Flux
以及 Mono
。
我创建了 GeneralValidator
class,效果很好 javax.validation.Validator
@Component
@RequiredArgsConstructor
public class GeneralValidator {
private final Validator validator;
private <T> void validate(T obj) {
if (obj == null) {
throw new IllegalArgumentException();
}
Set<ConstraintViolation<T>> violations = this.validator.validate(obj);
if (violations != null && !violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
/**
* @param obj object we will validate
* @param next Publisher we will call if don't have any validation hits
*/
public <T> Mono<ServerResponse> validateAndNext(T obj, Mono<ServerResponse> next) {
try {
validate(obj);
return next;
} catch (IllegalArgumentException ex) {
return ServerResponse.badRequest()
.body(new ErrorResponse("Request body is empty or unable to deserialize"), ErrorResponse.class);
} catch (ConstraintViolationException ex) {
return ServerResponse.badRequest()
.body(new ValidationErrorResponse(
"Request body failed validation",
ex.getConstraintViolations()
.stream()
.map(v -> "Field '%s' has value %s but %s"
.formatted(v.getPropertyPath(), v.getInvalidValue(),v.getMessage()))
.collect(Collectors.toList())
), ValidationErrorResponse.class);
}
}
}
使用方法:
...
.POST("/", req -> req.bodyToMono(RequestObject.class)
.flatMap(r -> validator.validateAndNext(r,routeFunction.execute(r)))
)
...
routeFunction.execute
:
public @NotNull Mono<ServerResponse> execute(RequestObject request) {
//handling body
}
在 Spring 控制器方法中,我们可以使用 @Valid
和类似这样的东西
@PostMapping(REGISTER)
public ResponseEntity<SomeData> registerSomeData(@RequestBody @Valid final SomeData someData) {
...................
}
public class SomeData {
@Size(min = 2, max = 20)
private String firstname;
@Digits(fraction = 0, integer = 10)
private Integer customerID;
@NotNull
private Customer customer;
}
如果请求不符合这些约束,那么 Spring 框架将抛出 Bad Request Exception(400)。
有了Spring5个路由器函数,我不明白我们怎么能做到这一点,因为我们不能在路由器函数中给出@Valid
。
您不能将基于注释的验证与(功能)Spring Webflux 一起使用。参见
如果您绝对需要基于注释的验证,您应该知道您可以继续使用传统的 Spring MVC 和 Spring 5(或非功能性 Webflux)。
有点恼火的是,这个有用的功能似乎并没有被应用到函数世界中,但是自己实现验证步骤确实并不难。方法如下。
创建一个 bean 来执行验证:
@Component
public class RequestValidator {
@Inject
Validator validator;
public <T> Mono<T> validate(T obj) {
if (obj == null) {
return Mono.error(new IllegalArgumentException());
}
Set<ConstraintViolation<T>> violations = this.validator.validate(obj);
if (violations == null || violations.isEmpty()) {
return Mono.just(obj);
}
return Mono.error(new ConstraintViolationException(violations));
}
}
现在在您的处理程序函数中包含一个执行验证的步骤。在此示例中,FindRequest
class 是一个 JSON 域模型 class,其中包含验证注释,例如 @NotEmpty
和 @NotNull
等。调整您的方式根据这个调用反应式数据存储库的虚构示例构建 ServerResponse
。
@Component
public class MyHandler {
@Inject
RequestValidator validator;
public Mono<ServerResponse> findAllPeople(ServerRequest request) {
return request.bodyToMono(FindRequest.class)
.flatMap(this.validator::validate)
.flatMap(fr -> ServerResponse
.ok()
.body(this.repo.findAllByName(fr.getName()), Person.class));
}
}
可以使用相同的方法来扩展功能以处理 Flux
以及 Mono
。
我创建了 GeneralValidator
class,效果很好 javax.validation.Validator
@Component
@RequiredArgsConstructor
public class GeneralValidator {
private final Validator validator;
private <T> void validate(T obj) {
if (obj == null) {
throw new IllegalArgumentException();
}
Set<ConstraintViolation<T>> violations = this.validator.validate(obj);
if (violations != null && !violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
/**
* @param obj object we will validate
* @param next Publisher we will call if don't have any validation hits
*/
public <T> Mono<ServerResponse> validateAndNext(T obj, Mono<ServerResponse> next) {
try {
validate(obj);
return next;
} catch (IllegalArgumentException ex) {
return ServerResponse.badRequest()
.body(new ErrorResponse("Request body is empty or unable to deserialize"), ErrorResponse.class);
} catch (ConstraintViolationException ex) {
return ServerResponse.badRequest()
.body(new ValidationErrorResponse(
"Request body failed validation",
ex.getConstraintViolations()
.stream()
.map(v -> "Field '%s' has value %s but %s"
.formatted(v.getPropertyPath(), v.getInvalidValue(),v.getMessage()))
.collect(Collectors.toList())
), ValidationErrorResponse.class);
}
}
}
使用方法:
...
.POST("/", req -> req.bodyToMono(RequestObject.class)
.flatMap(r -> validator.validateAndNext(r,routeFunction.execute(r)))
)
...
routeFunction.execute
:
public @NotNull Mono<ServerResponse> execute(RequestObject request) {
//handling body
}