我是怎么进行SpringMVC参数校验的?

前语:

不要为了读文章而读文章,必定要带着问题来读文章,勤思考。java

在 Web 开发中, 咱们常常须要校验各类参数,这是一件繁琐又重要的事情,对于不少人来讲,在作参数校验的时候,会有如下几种类型的处理方式。ajax

甩锅型

校验太麻烦了,让客户端去负责校验就好了,调用方传错了是调用方的问题,不是服务的问题,甩个 500 错误让他们好好检讨:json

劳模型

有多少参数,我就写多少个 if 语句作判断,校验不经过的都写一句友好的提示,如:浏览器

工具型

本身写个参数校验的通用工具,而后每一个请求接收到的参数都调用工具方法来校验,校验不经过就把校验结果返回给调用方:bash

半自动型

对 SpringMVC 了解比较全面的朋友都知道,它支持 Bean Validation,所以能够经过使用 javax.validation.constraints 包下的注解,如 @NotNull@Max@Min 等,来实现由框架处理数据校验。框架

首先,添加 hibernate-validator 依赖(SpringBoot 已经为咱们自动添加了)。工具

<dependency>
    
<groupId>
org.hibernate.validator
</groupId>
    
<artifactId>
hibernate-validator
</artifactId>
    
<version>
6.0.10.Final
</version>
</dependency>复制代码

而后,在参数对象的字段上打注解:ui

最后,在 Controller 中给参数对象添加 @Valid 注解,并处理校验结果:spa

Tip:若是你的参数不是对象,必定要在 Controller 上打 @Validated 注解!hibernate

这样作,每一个 Controller 方法都要处理结果,也是很麻烦。

方案分析

以上这些处理方式都有不足之处:

首先,参数校验是一件很是重要的事,客户端要把住第一道防线,而服务方要采起不信任的态度,作好参数校验。不然非法请求参数小则影响用户体验或者产生垃圾数据,大则会拖跨整个系统!

其次,手工对全部的参数进行校验至关繁琐,容易出错,并且 So boring~

最后,经过工具来完成是比较好的方式,可是必须让工具变得优雅一些。

那么,有没有更好的解决方案呢?答案是:有的!

最佳实践

其实,上面的半自动型的解决方式,只要再进一步,就能够实现全自动了!

想一想,若是上面的半自动型例子中,咱们不在 Controller 方法中处理校验结果,会怎么样呢?答案是,会抛出异常:

那么,若是咱们作了全局统一异常处理,不就能够实现自动校验并返回咱们想要的结果了吗?因此咱们能够这样作:

@ControllerAdvice
public class 
GlobalExceptionHandler
 {
    
/** 统一处理参数校验异常 */
    
@ExceptionHandler
    
@ResponseBody
    public 
ResultBean
<?> handleValidationException(
BindException
 e) {
        
// 获取
        
String
 msg = e.getBindingResult().getAllErrors().stream()
                .map(
DefaultMessageSourceResolvable
::getDefaultMessage)
                .collect(
Collectors
.joining(
","
));
        log.warn(
"参数校验不经过, msg: {}"
, msg);
        return 
ResultBean
.fail(msg);
    }
}复制代码

然而,若是你只处理 BindException 这个异常的话,你会发现这个方案有时候好用,有时候却会“失灵”。为何呢?由于对于不一样的参数解析方式,Spring作参数校验时会抛出不一样的异常,并且这些异常没有继承关系,经过异常获取校验结果的方式也各不相同(好坑爹~)。

总结起来有如下几种异常须要处理:

对象参数接收请求体,即 RequestBody:

MethodArgumentNotValidException

请求参数绑定到对象参数上:

BindException

普通参数:

ConstraintViolationException
必填参数缺失:
ServletRequestBindingException

因此完整的处理方法应该是这样:

@ExceptionHandler
({
ConstraintViolationException
.class,
            
MethodArgumentNotValidException
.class,
            
ServletRequestBindingException
.class,
            
BindException
.class})
@ResponseBody
public 
ResultBean
<?> handleValidationException(
Exception
 e) {
    
String
 msg = 
""
;
    if (e instanceof 
MethodArgumentNotValidException
) {
        
MethodArgumentNotValidException
 t = (
MethodArgumentNotValidException
) e;
        msg = t.getBindingResult().getAllErrors().stream()
               .map(
DefaultMessageSourceResolvable
::getDefaultMessage)
               .collect(
Collectors
.joining(
","
));
    } else if (e instanceof 
BindException
) {
        
BindException
 t = (
BindException
) e;
        msg = t.getBindingResult().getAllErrors().stream()
               .map(
DefaultMessageSourceResolvable
::getDefaultMessage)
               .collect(
Collectors
.joining(
","
));
    } else if (e instanceof 
ConstraintViolationException
) {
        
ConstraintViolationException
 t = (
ConstraintViolationException
) e;
        msg = t.getConstraintViolations().stream()
                .map(
ConstraintViolation
::getMessage)
                .collect(
Collectors
.joining(
","
));
    } else if (e instanceof 
MissingServletRequestParameterException
) {
        
MissingServletRequestParameterException
 t = (
MissingServletRequestParameterException
) e;
        msg = t.getParameterName() + 
" 不能为空"
;
    } else if (e instanceof 
MissingPathVariableException
) {
        
MissingPathVariableException
 t = (
MissingPathVariableException
) e;
        msg = t.getVariableName() + 
" 不能为空"
;
    } else {
        msg = 
"必填参数缺失"
;
    }
    log.warn(
"参数校验不经过,msg: {}"
, msg);
    return 
ResultBean
.fail(msg);
}复制代码

添加了这个全局异常处理器以后,就能够自动参数校验了!体验飞升的感受~~

可是,若是用户是在浏览器上访问某个页面,而后参数校验不经过时这个统一异常处理器会返回一个 json 格式的文本。普通用户看到这样的文本,估计要懵圈了。

那么问题来了,怎么才能让打开页面的请求返回错误页面,而 ajax 请求返回 json 呢?个人实例代码已经展现了,有兴趣能够了解一下。


---------------------

相关文章
相关标签/搜索