一、前言
1. 传统参数校验的不足
在上一篇文章中,已经详细的讲解了自定义全局异常捕获,今天接着写参数校验相关的。在日常的项目中,我们可能忽略了参数校验的重要性,参数校验可以限制用户的输入,提高系统的安全性,使程序更加的健壮。在日常的项目中,我们可能直接在controller层做参数校验,比如发现某个必传参数前端没有传过来,然后后端判断一下,发现为空,直接返回给前端参数缺失。
在我们日常开发中,数据校验的实现是一件比较痛苦的事情,繁琐且无趣,对于一般的业务而言,极少出现一些过于复杂的校验,常常都是非空,长度,最大最小值,正则,数据关联,定值等等。我在开发过程中为了减少我写这些代码的coding成本,由此认识了javax.validation包下的 @Valid 注解 和 Java 的 Bean Validation 规范。
如果按照规范,controller层不应该放参数判断相关的逻辑,同时如果参数非常多,都需要判断,那么将会非常的麻烦,那么如何优雅的解决这个问题呢?
2. 解决方案
我们其实可以通过自定义异常捕获+springboot自带的validation来解决这个问题,在处理参数的时候配合异常处理,只要加几个注解就能轻松搞定参数校验,自定义异常捕获在上一篇文章中已经详细讲到,没有看过的可以看我的上一篇文章地址是【springboot框架增强】优雅的自定义异常捕获 全网最新
下面我们一起来看学习参数校验吧
二、几种传递参数的形式
1. Get请求通过拼接url进行传递
相当于把参数拼接在url后面,然后在controller方法上加上@PathVariable
注解,参数要和{}中的名称对应上。
举例子:
@GetMapping("getUserByOpenid/{openid}")
@CustomizeLog
public ReturnMsgEntity getUserByOpenid(@PathVariable @NeedAttention String openid) {
return null;
}
@CustomizeLog和@NeedAttention注解不需要管,下篇文章会详细讲解自定义注解的使用方法
这个时候我们访问路径的形式如下:
2. Get请求通过?拼接参数
查询参数
举例子:
@GetMapping("getUserById")
public ReturnMsgEntity findUserById(@RequestParam(value = "id", required = false)
Integer id) {
return null;
}
3. Post请求传递Body参数
使用@RequestBody
注解。
举例子:
@PostMapping("save")
public ReturnMsgEntity save(@RequestBody @Validated WxuserDTO wxuserDTO) {
return null;
}
以上简单的介绍了参数传递的形式,下面开始介绍如何利用注解进行参数校验。
三、使用javax.validation.constraints的一些注解,进行参数校验
1. JSR(Java Specification Requests)
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
2. JSR-303
是Java EE 6 中的一项子规范,叫做Bean Validation,hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
通俗易懂的讲,Bean Validation的参数校验机制是JSR-303的一种实现方式。
里面有大概有这些注解:
3. 使用注解的方法
3.1 get请求的使用方法
- 在类上加
Validated
注解 - 在方法请求参数前加注解和提示 举例子:
@GetMapping("getUserById")
public ReturnMsgEntity findUserById(@RequestParam(value = "id", required = false)
@Max(value = 10, message = "id最大值不超过10")
@NotNull(message = "id不能为空") Integer id) {
return null;
}
请求方式:
上面的结果并不是我们想要的,我们想要我们自己定义的,这时候就需要自定义处理异常了。
首先看报错信息:
我们要捕获
ConstraintViolationException
异常,同时获取到里面的信息,封装成我们自己的ReturnMsgEntity
,接着上篇文章点我,自定义异常捕获链接,具体代码如下:
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ReturnMsgEntity handlerConstraintException(HttpServletRequest request, ConstraintViolationException e) {
String requestUrl = request.getRequestURI();
String method = request.getMethod();
// for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
// constraintViolation.getMessage();
// }
String messages = e.getMessage();
return new ReturnMsgEntity(10001, messages, null, method + " " + requestUrl);
}
这时候我们再请求,就是我们想要的了
3.1 post请求的使用方法
- 在参数上要加@Validated注解
- 在实体类的属性上就可以加各种注解了
- 如果一个实体类中又嵌套实体类,需要在第一个实体类中定义第二个实体类的属性上加上@Valid注解。
和get方法类似,也是捕获异常,在GlobalExceptionAdvice
类中新增
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ReturnMsgEntity handlerBeanValidation(HttpServletRequest request, MethodArgumentNotValidException e) {
String requestUrl = request.getRequestURI();
String method = request.getMethod();
List<ObjectError> errors = e.getBindingResult().getAllErrors();
String messages = formatAllErrorMessages(errors);
return new ReturnMsgEntity(10001, messages, null, method + " " + requestUrl);
}
/**
* 处理?里的参数校验
* @param request
* @param e
* @return
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ReturnMsgEntity handlerMissingServletRequestParameter(HttpServletRequest request, MissingServletRequestParameterException e) {
String requestUrl = request.getRequestURI();
String method = request.getMethod();
// List<ObjectError> errors = e.getBindingResult().getAllErrors();
// String messages = formatAllErrorMessages(errors);
return new ReturnMsgEntity(10001, "错误", null, method + " " + requestUrl);
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ReturnMsgEntity handlerConstraintException(HttpServletRequest request, ConstraintViolationException e) {
String requestUrl = request.getRequestURI();
String method = request.getMethod();
// for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
// constraintViolation.getMessage();
// }
String messages = e.getMessage();
return new ReturnMsgEntity(10001, messages, null, method + " " + requestUrl);
}
private String formatAllErrorMessages(List<ObjectError> errors) {
StringBuffer errorMsg = new StringBuffer();
errors.forEach(error -> {
errorMsg.append(error.getDefaultMessage()).append(";");
});
return errorMsg.toString();
}
结果:
四、封装message
前几步已经完成了自定义校验,但提示信息都存放硬编码在类中,有没有方法把信息都存在配置文件中呢?
当然有,我们可以通过在resource下新建ValidationMessages.properties
,从而达到目的。
1. ValidationMessages.properties配置文件的创建
在resource下建立ValidationMessages.properties,同时根据业务定义一些消息提示
四、 结尾
以上大概是参数校验的详细讲解,可以解决大部分问题,但是如果是很复杂的校验该怎么办呢?篇幅有限,下一篇来讲解自定义注解,来解决复杂校验,以及自定义注解的一些场景举例。