资讯专栏INFORMATION COLUMN

Spring 参数校验最佳实践(附完整实例)

tomlingtm / 1760人阅读

摘要:否则非法请求参数小则影响用户体验或者产生垃圾数据,大则会拖跨整个系统其次,手工对所有的参数进行校验相当繁琐,容易出错,而且最后,通过工具来完成其实是比较好的方式,但是必须让工具变得优雅一些。

声明:本文属原创文章,始发于公号:程序员自学之道,同步发布到 sf,转载请注明出处。

不够好的方案

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

甩锅型

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

劳模型

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

工具型

自己写个参数校验的通用工具,然后每个请求接收到的参数都调用工具方法来校验,校验不通过就把校验结果返回给调用方。这样确实能减少很多冗余的代码:

半自动型

对 SpringMVC 了解比较全面的朋友都知道,它支持 Bean Validation,因此可以通过使用 javax.validation.constraints 包下的注解,如 @NotNull @Max @Min 等,来实现由框架处理数据校验:
首先,添加 hibernate-validator 依赖(SpringBoot 项目为我们自动添加了):


    org.hibernate.validator
    hibernate-validator
    6.0.10.Final

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

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

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

这样做,每个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做参数校验时会抛出不同的异常,而且这些异常没有继承关系,通过异常获取校验结果的方式也各不相同(好坑爹~)。

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

对象参数接收请求体: MethodArgumentNotValidException

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

普通参数ConstraintViolationException

必填参数没传: ServletRequestBindingException

必填请求参数缺失:MissingServletRequestParameterException

路径参数缺失:MissingPathVariableException

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

@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 = getBindingResultMsg(t.getBindingResult());
    } else if (e instanceof BindException) {
        BindException t = (BindException) e;
        msg = getBindingResultMsg(t.getBindingResult());
    } 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);
}

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

完整实例已经上传到 GitHub,请查看:https://github.com/dadiyang/s...

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

那么问题来了,怎么能让打开页面的请求返回错误页面,而 ajax 请求返回 json 呢

我的实例代码已经展示了,有兴趣可以了解一下,敬请期待下一篇的讲解。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74782.html

相关文章

  • Dockerfile最佳实践

    摘要:比如和指令,镜像中的文件内容被检查并且为每个文件计算校验和。这些文件的最终修改和访问时间将不被考虑到校验和内。在查找缓存期间,校验和将被用于与已存在的镜像校验和进行对比。 Docker 可以从 Dockerfile 中读取指令自动构建镜像,Dockerfile是一个包含构建指定镜像所有命令的文本文件。Docker坚持使用特定的格式并且使用特定的命令。你可以在 Dockerfile参考 ...

    张金宝 评论0 收藏0
  • 【推荐】最新200篇:技术文章整理

    摘要:作为面试官,我是如何甄别应聘者的包装程度语言和等其他语言的对比分析和主从复制的原理详解和持久化的原理是什么面试中经常被问到的持久化与恢复实现故障恢复自动化详解哨兵技术查漏补缺最易错过的技术要点大扫盲意外宕机不难解决,但你真的懂数据恢复吗每秒 作为面试官,我是如何甄别应聘者的包装程度Go语言和Java、python等其他语言的对比分析 Redis和MySQL Redis:主从复制的原理详...

    BicycleWarrior 评论0 收藏0
  • 【推荐】最新200篇:技术文章整理

    摘要:作为面试官,我是如何甄别应聘者的包装程度语言和等其他语言的对比分析和主从复制的原理详解和持久化的原理是什么面试中经常被问到的持久化与恢复实现故障恢复自动化详解哨兵技术查漏补缺最易错过的技术要点大扫盲意外宕机不难解决,但你真的懂数据恢复吗每秒 作为面试官,我是如何甄别应聘者的包装程度Go语言和Java、python等其他语言的对比分析 Redis和MySQL Redis:主从复制的原理详...

    tommego 评论0 收藏0

发表评论

0条评论

tomlingtm

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<