1. 简介
项目中使用的spring validate + jsr303对接口的入参对象进行校验。
今天在项目中编写某一个功能时,需要传入一个List对象的数据。然后便产生了对标题所描述问题的思考:JSR303如何校验List中的对象?
2. 问题描述
如下代码:
public ResponseResult orderSent(@Validate @RequestBody List<OrderSentQuery> orderSentQuerys){
//do something
}
public class OrderSentQuery{
/**
* 必填
* 店铺编号
*/
@NotNull(message = "店铺编号不能为空")
@ApiModelProperty(value = "店铺编号", example = "14536871")
private Integer shopId;
/**
* 必填
* 内部单号
*/
@NotNull(message = "内部单号不能为空")
@ApiModelProperty(value = "内部单号", example = "291814")
private Integer oId;
/**
* 必填
* 线上单号
*/
@NotEmpty(message = "线上单号不能为空")
@ApiModelProperty(value = "线上单号", example = "202106166232206600301205")
private String soId;
/**
* 必填
* 快递公司
*/
@NotEmpty(message = "快递公司不能为空")
@ApiModelProperty(value = "快递公司", example = "顺丰快递")
private String lcName;
/**
* 必填
* 快递单号
*/
@NotEmpty(message = "快递单号不能为空")
@ApiModelProperty(value = "快递单号", example = "SF1553517894539045676")
private String lId;
/**
* 必填
* 快递编码,【物流(快递)公司及打印模板】中的物流公司编号
*/
@NotEmpty(message = "快递编码不能为空")
@ApiModelProperty(value = "【物流(快递)公司及打印模板】中的物流公司编号", example = "SF")
private String lcId;
}
上述代码中存在的问题时JSR303不会对OrderSentQuery中的属性进行校验的。所以我们就需要换种方式去解决它。
3. 问题解决
@ApiOperation("订单发货")
@ResponseBody
@RequestMapping(value = "/orderSent", method = RequestMethod.POST)
public ResponseResult orderSent(@Validated @RequestBody OrderSentQuery orderSentQuery) throws IOException {
logger.info("订单发货:{}", orderSentQuery.toString());
orderManager.orderSent(orderSentQuery);
return new SuccessResponseResult();
}
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
/**
* @description:
* @author: WangYX
* @create: 2022-02-17 17:55
* @Version: 1.0.0
**/
@Data
public class OrderSentQuery {
@Valid
@NotNull(message = "数据不能为空")
@Size(min = 1)
List<Sent> sent;
@Data
@Valid
public static class Sent {
/**
* 必填
* 店铺编号
*/
@NotNull(message = "店铺编号不能为空")
@ApiModelProperty(value = "店铺编号", example = "14536871")
private Integer shopId;
/**
* 必填
* 内部单号
*/
@NotNull(message = "内部单号不能为空")
@ApiModelProperty(value = "内部单号", example = "291814")
private Integer oId;
/**
* 必填
* 线上单号
*/
@NotEmpty(message = "线上单号不能为空")
@ApiModelProperty(value = "线上单号", example = "202106166232206600301205")
private String soId;
/**
* 必填
* 快递公司
*/
@NotEmpty(message = "快递公司不能为空")
@ApiModelProperty(value = "快递公司", example = "顺丰快递")
private String lcName;
/**
* 必填
* 快递单号
*/
@NotEmpty(message = "快递单号不能为空")
@ApiModelProperty(value = "快递单号", example = "SF1553517894539045676")
private String lId;
/**
* 必填
* 快递编码,【物流(快递)公司及打印模板】中的物流公司编号
*/
@NotEmpty(message = "快递编码不能为空")
@ApiModelProperty(value = "【物流(快递)公司及打印模板】中的物流公司编号", example = "SF")
private String lcId;
}
}
将List类型的数据转换成入参对象中的一个属性,并且在List对象上添加@Valid注解,以及在List的泛型对象上也要添加@Valid注解。
在编写的过程中还遇到了一个问题:Sent这个内部类需要添加static,设置为静态的。否则在调用接口的时候会抛出如下异常:
non-static inner classes like this can only by instantiated using default, no-argument constructor
问题描述:
服务端使用 @RequestBody 来接受参数,web端使用json传递数据的时候,报错。
问题的本质原因是:内部非静态类无法实例化。
//错误示例
public class A {
class B;
class B{
int id ;
}
}
//示例正确
public class A {
class B;
static class B implements Serializable{
int id ;
}
}
我这里出现上述异常的原因是采用内部类这种类型的数据。如果把Sent定义成普通的POJO,可以完全忽略上述的问题。
4. JSR303的使用及配置
- 引入依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
如果在项目中引入了spring-boot-starter-web
则不需要在另外引入jar包,在spring-boot-starter-web中已经引入了对应的jar包。
2. 添加JSR303的配置
/**
* Function:配置 JSR303 Bean Validator 定义
*
* @author Wangyx
* @date 2021/12/28 16:31
* @version 1.0.0
*/
@Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
- 在全局的异常处理中拦截JSR303中出现的异常。
/**
* Function:数据校验异常处理
*
* @author Wangyx
* @date 2021/12/28 17:54
* @version 1.0.0
*/
@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
@ResponseBody()
public ErrorResponseResult handleValidException(Exception e) {
BindingResult bindingResult = null;
if (e instanceof MethodArgumentNotValidException) {
bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
} else if (e instanceof BindException) {
bindingResult = ((BindException) e).getBindingResult();
}
Map<String, String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError) -> errorMap.put(fieldError.getField(), fieldError.getDefaultMessage()));
return new ErrorResponseResult(new CommonException(CommonExceptionEnum.PARAME_ERROR, CommonExceptionEnum.PARAME_ERROR.getMsg() + ":" + errorMap.toString()));
}
这样就可以避免在业务代码中处理JSR303中出现的异常。