Spring WebFlux中如何校验请求DTO?
很多刚接触Spring WebFlux的朋友,都会疑惑怎么校验请求DTO——毕竟在传统的Spring MVC里,只要给DTO加个@NotBlank、@Email这类校验注解,再在控制器参数上标注@Valid就搞定了,那WebFlux里是不是不一样?别慌,其实思路差不多,只是细节上有一点点区别,下面我就给你一步步说清楚。
首先先复习下传统Spring MVC的做法,方便对比:
我们会先给DTO类加上校验注解,比如:
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Size; public class UserSignupRequestDTO { @NotBlank(message = "邮箱不能为空") @Email(message = "请输入合法的邮箱格式") private String email; @NotBlank(message = "密码不能为空") @Size(min = 6, max = 20, message = "密码长度需在6到20位之间") private String password; // 省略getter、setter }
然后在控制器方法的参数前加上@Valid,Spring就会自动帮我们做校验:
@PostMapping("/signup") public ResponseEntity<String> signup(@Valid @RequestBody UserSignupRequestDTO signupRequest) { // 处理注册逻辑 return ResponseEntity.ok("注册成功"); }
那到了WebFlux里,该怎么操作呢?其实分几步就能搞定:
1. 确保引入校验依赖
首先要保证项目里有Spring Validation的依赖,如果是Spring Boot项目,直接在pom.xml(Maven)或者build.gradle(Gradle)里加入:
Maven
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
Gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
WebFlux本身不会默认包含这个依赖,所以必须手动引入,不然校验注解根本不会生效。
2. DTO类的校验注解和MVC完全一致
不用改任何东西,还是用jakarta.validation.constraints包下的那些注解,比如刚才的UserSignupRequestDTO直接复用就行,写法和MVC里一模一样。
3. 控制器里的校验写法
WebFlux里我们通常用Mono或者Flux来接收请求体,这时候要把@Valid直接标注在Mono/Flux前面,而不是泛型里面,比如:
import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import jakarta.validation.Valid; import reactor.core.publisher.Mono; @RestController public class UserController { @PostMapping("/signup") public Mono<ResponseEntity<String>> signup(@Valid @RequestBody Mono<UserSignupRequestDTO> signupRequestMono) { return signupRequestMono .map(dto -> { // 这里写你的注册业务逻辑,比如保存用户到数据库 return ResponseEntity.ok("注册成功"); }); } }
4. 处理校验失败的异常
当校验不通过时,WebFlux会抛出ConstraintViolationException,我们可以用两种方式处理:
方式一:在接口里单独处理
通过onErrorResume来捕获异常,返回友好的错误响应:
@PostMapping("/signup") public Mono<ResponseEntity<String>> signup(@Valid @RequestBody Mono<UserSignupRequestDTO> signupRequestMono) { return signupRequestMono .map(dto -> ResponseEntity.ok("注册成功")) .onErrorResume(ConstraintViolationException.class, ex -> { // 提取错误信息 String errorMsg = ex.getConstraintViolations() .stream() .map(violation -> violation.getMessage()) .findFirst() .orElse("请求参数校验失败"); return Mono.just(ResponseEntity.badRequest().body(errorMsg)); }); }
方式二:全局异常处理器(更推荐)
如果多个接口都需要处理校验异常,写一个全局异常处理器会更优雅,不用每个接口都重复写错误处理逻辑:
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import jakarta.validation.ConstraintViolationException; import java.util.stream.Collectors; @RestControllerAdvice public class GlobalValidationExceptionHandler { @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<String> handleValidationException(ConstraintViolationException ex) { String errorMessages = ex.getConstraintViolations() .stream() .map(violation -> violation.getMessage()) .collect(Collectors.joining("; ")); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMessages); } }
这样一来,所有接口的校验异常都会被这个处理器捕获,统一返回格式化的错误信息。
总结一下,WebFlux里的DTO校验和MVC的核心逻辑是一致的,只是因为响应式编程的特性,在控制器参数的标注和异常处理上有一点点区别,只要跟着上面的步骤来,就能轻松搞定啦!
内容来源于stack exchange

