SpringMVC参数校验的三种方案(普通、异常处理、AOP)

目录
  1. 方案一:@Valid + BindingResult
  2. 方案二:使用@Valid,不使用BindingResult。
  3. 方案三:不使用@Valid和BindingResult

SpringMVC参数校验一般使用hibernate-validator校验框架,网上相关注解资料很多,本文不做介绍,本文主要介绍使用如何降低校验编程的复杂度。

方案一:@Valid + BindingResult

使用@Valid + BindingResult进行参数校验,网上大部分参数校验文章都采用这种方式,示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// VO
@Data
public class DeviceStorageRecordVO {
@NotNull(message = "数量不能为空")
private Integer count;
}

// Controller
@RestController
@RequestMapping("device")
public class DeviceCtrl {

@PostMapping("storageRecord/add")
public JsonResult addStorageRecord(@Valid @RequestBody DeviceStorageRecordVO deviceStorageRecordVO, BindingResult result) {
// 方法1: 处理result
StringBuilder errorBuilder = new StringBuilder();
List<ObjectError> errors = result.getAllErrors();

for(ObjectError error : errors){
if(StringUtils.hasText(errorBuilder)) {
errorBuilder.append(" ");
}
errorBuilder.append(error.getDefaultMessage());
}

if(StringUtils.hasText(errorBuilder.toString())){
throw new BusiException(ResultEnum.PARAMS_ILLEGAL.getCode(),errorBuilder.toString());
}

deviceServ.addStorageRecord(deviceStorageRecordVO);
return MVCUtil.success();
}
}

// 方法2 Controller AOP
@Aspect
@Component
@Slf4j
public class WebAspect {

// AOP的一些配置:控制器切面,切入点..


// 关键方法:参数校验
private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
private void checkParams(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Parameter[] ps = method.getParameters();
StringBuilder errorBuilder = new StringBuilder();
for (int i = 0; i < ps.length; i++) {
Parameter p = ps[i];
Annotation annotation = p.getAnnotation(Valid.class);
// Valid判断可以不要, 每个参数都会校验
if (annotation != null) {
Set violations = validator.validate(args[i]);
for (Object violation : violations) {
ConstraintViolation constraintViolation = (ConstraintViolation)violation;
if(StringUtils.isNotBlank(errorBuilder)) {
errorBuilder.append(" ");
}
errorBuilder.append(constraintViolation.getMessage());
}
}
}
if(StringUtils.isNotBlank(errorBuilder.toString())){
throw new BusiException(ResultEnum.PARAMS_ILLEGAL.getCode(),errorBuilder.toString());
}
}
}

在方法1处处理result值,可进行封装 或 如方法2一样使用AOP统一处理。

缺点:麻烦,遗忘添加@Valid 或 BindingResult未跟在@Valid标记的Bean后面均会导致校验无效或应用报错(MethodArgumentNotValidException异常)。

方案二:使用@Valid,不使用BindingResult。

当使用@Valid而未添加BindingResult进行参数校验时,应用会报MethodArgumentNotValidException异常,此时使用ControllerAdvice进行异常的统一处理即可,在方法里进行错误参数message的组装返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class ExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public JsonResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
BindingResult result = e.getBindingResult();
StringBuilder errorBuilder = new StringBuilder();
List<ObjectError> errors = result.getAllErrors();

for(ObjectError error : errors) {
if(StringUtils.hasText(errorBuilder)) {
errorBuilder.append(" ");
}
errorBuilder.append(error.getDefaultMessage());
}

if(StringUtils.hasText(errorBuilder.toString())){
return MVCUtil.error(ResultEnum.PARAMS_ILLEGAL, errorBuilder.toString());
}
return MVCUtil.error(ResultEnum.PARAMS_ILLEGAL, "参数非法");
}
}

优点:不需要添加BindingResult,错误统一处理,代码复杂度降低。

方案三:不使用@Valid和BindingResult

不使用@Valid和BindingResult而在AOP方法里手动进行校验。去掉@Valid注解及BindingResult参数并使用方案一中的AOP,去掉其中的@Valid注解判断(使得所有参数都进行校验)即可。

优点:代码复杂度进一步降低,只需要在需要校验的Bean中使用校验注解进行标记即可。


方案三代码量及复杂度最低,但由于历史原因项目使用了@Valid,担心大量修改导致系统出现一些故障,故项目采用方案二、方案三同时使用,老代码不做修改,新代码直接使用方案三。