不要为了读文章而读文章,一定要带着问题来读文章,勤思考。
在 Web 开发中, 我们经常需要校验各种参数,这是一件繁琐又重要的事情,对于很多人来说,在做参数校验的时候,会有以下几种类型的处理方式。
校验太麻烦了,让客户端去负责校验就行了,调用方传错了是调用方的问题,不是服务的问题,甩个 500 错误让他们好好反省:
有多少参数,我就写多少个 if 语句做判断,校验不通过的都写一句友好的提示,如:
自己写个参数校验的通用工具,然后每个请求接收到的参数都调用工具方法来校验,校验不通过就把校验结果返回给调用方:
对 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>复制代码
然后,在参数对象的字段上打注解:
最后,在 Controller 中给参数对象添加 @Valid 注解,并处理校验结果:
Tip:如果你的参数不是对象,一定要在 Controller 上打 @Validated 注解!
这样做,每个 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 呢?我的实例代码已经展示了,有兴趣可以了解一下。
---------------------
版权声明:本文为博主原创文章,转载请附上博文链接!