美文网首页Java 杂谈Java架构进阶
我是怎么进行SpringMVC参数校验的?

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

作者: java架构进阶 | 来源:发表于2019-07-02 21:27 被阅读2次

    前语
    不要为了读文章而读文章,一定要带着问题来读文章,勤思考。

    在 Web 开发中, 我们经常需要校验各种参数,这是一件繁琐又重要的事情,对于很多人来说,在做参数校验的时候,会有以下几种类型的处理方式。

    甩锅型

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

    劳模型

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

    工具型

    自己写个参数校验的通用工具,然后每个请求接收到的参数都调用工具方法来校验,校验不通过就把校验结果返回给调用方:。

    半自动型

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

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

    <pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232);"><dependency>
    
    <groupId>
    org.hibernate.validator
    </groupId>
    
    <artifactId>
    hibernate-validator
    </artifactId>
    
    <version>
    6.0.10.Final
    </version>
    </dependency>
    </pre>
    

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

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

    Tip:如果你的参数不是对象,一定要在 Controller 上打 @Validated 注解!

    [图片上传中...(image-657a49-1562073850576-1)]

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

    方案分析

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

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

    其次,手工对所有的参数进行校验相当繁琐,容易出错,而且 So boring~

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

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

    最佳实践

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

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

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

    那么,如果我们做了全局统一异常处理,不就可以实现自动校验并返回我们想要的结果了吗?所以我们可以这样做:

    <pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232);">@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);
     }
    }
    </pre>
    

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

    总结起来有以下几种异常需要处理:

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

    MethodArgumentNotValidException

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

    BindException

    普通参数:

    ConstraintViolationException

    必填参数缺失:

    ServletRequestBindingException

    所以完整的处理方法应该是这样:

    <pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232);">@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);
    }
    </pre>
    

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

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

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

    相关文章

      网友评论

        本文标题:我是怎么进行SpringMVC参数校验的?

        本文链接:https://www.haomeiwen.com/subject/nqhxhctx.html