@Valid|@Valid 无法校验List的问题
目录
- @Valid 无法校验List
- 原因
- 方法
- @validated 验证List
- 反面例子
- 解决方法
@Valid 无法校验List
原因
@Valid只能校验JavaBean,而List
方法
方法1:对List进行Wrapper
既然List不是JavaBean,那我们就把它封装成JavaBean,我们定义一个ListWrapper类如下:
package com.wyq.firstdemo.util; import lombok.Getter; import lombok.Setter; import javax.validation.Valid; import java.util.ArrayList; import java.util.List; @Setter@Getterpublic class ListWrapper{@Validprivate List list; public ListWrapper() {list = new ArrayList<>(); } publicListWrapper(List list) {this.list = list; } }
同时修改一下controller对应的方法:
// 使用包装类对list进行验证@PostMapping("/insert/all")public ServerResponse insertList(@Valid @RequestBody ListWrapperlistWrapper, BindingResult bindingResult) {if(bindingResult.hasErrors()) {log.error(bindingResult.getFieldError().toString()); return ServerResponse.createByErrorMessage(bindingResult.getFieldError().getDefaultMessage()); } userService.insertList(listWrapper.getList()); return ServerResponse.createBySuccess(); }
这样就可以对list进行校验了
注意:
由于对list进行了包装,如果我们传参的时候
[{},{}..]要改为{“list”: [{},{}..]}
方法2:使用@Validated+@Valid
在controller类上面增加@Validated注解,并且删除方法参数中的BindingResult bindingResult(因为这个参数已经没有用了,异常统一有controller返回了)
文章图片
文章图片
然后我们运行一下测试一下
文章图片
可以看到可以对参数进行校验了,但还还有一个问题,那就是这个不是我们想要的返回格式,它controller自己返回的格式,所以我们需要做一个统一异常处理,代码如下:
package com.wyq.firstdemo.handler; import com.wyq.firstdemo.common.ServerResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.util.Set; @Slf4j@RestControllerAdvicepublic class ControllerExceptionHandler { @ExceptionHandler@ResponseBodypublic ServerResponse handle(ConstraintViolationException exception) {log.error(String.valueOf(exception)); Set> violations = exception.getConstraintViolations(); StringBuilder builder = new StringBuilder(); for (ConstraintViolation violation : violations) {builder.append(violation.getMessage()); break; }return ServerResponse.createByErrorMessage(builder.toString()); } }
经过统一异常处理,我们这边的返回结果就是我们想要的格式了
文章图片
方法3:自定义一个List
先上代码后说明,先定义一个ValidList
package com.wyq.firstdemo.util; import javax.validation.Valid; import java.util.*; public class ValidListimplements List { @Validprivate List list; public ValidList() {this.list = new ArrayList<>(); } public ValidList(List list) {this.list = list; } public List getList() {return list; } public void setList(List list) {this.list = list; } @Overridepublic int size() {return list.size(); } @Overridepublic boolean isEmpty() {return list.isEmpty(); } ... }
对比方法3和方法1,有没有觉得代码有点相似,新建一个类,并且让他实现List接口,使这个类即具有了JavaBean的特性,又具有了List的特性,比方法1简单优雅很多。
只需要把List换成ValidList就可以了,还不需要多统一异常处理。
文章图片
参考:https://stackoverflow.com/questions/28150405/validation-of-a-list-of-objects-in-spring/36313615#36313615
@validated 验证List 经常遇到一些参数需要验证,用@validated 的分组验证方式很棒,可以解决大量的冗余代码,提升美观性。
但是我们平时会遇到入参为list的校验,@validated 的分组验证就无法使用了,非常麻烦,各大搜索引擎一查,总结一下比较推荐的方法:
反面例子
@***Mapping("/**")public *** apiName(@RequestBody @Validated(Add.class) List aObject)
正常情况下这个例子是无法使用的,不是接口无法使用,是@Validated无效。这是因为你的入参实体是List,其内并没有调用AClass的@Valid,导致你的校验规则只校验的List本身,并不校验其内部实体。
解决方法
在项目里添加一个ValidList类即可,此类通用,可以在全部由此需求的项目(jdk1.8)中添加,无需改动,有ValidList类之后只要将接口方法参数中的List改成ValidList即可:
@***Mapping("/**")public *** apiName(@RequestBody @Validated(Add.class) ValidListaObject)
上代码:
注意这个构造方法,网上的例子就少一点东西,会让你很难用,我优化了一下。
package com.analog.greatbolderserver.config.valid; import lombok.Data; import lombok.NoArgsConstructor; import javax.validation.Valid; import java.util.*; /** * @ClassName ValidList * @Description ValidList * @Author TY * @Date 2020/8/26 16:05 * @Version 1.0 **/@Data@NoArgsConstructorpublic class ValidListimplements List { @Validprivate List list = new LinkedList<>(); public ValidList(List paramList) {this.list = paramList; } @Overridepublic int size() {return list.size(); } @Overridepublic boolean isEmpty() {return list.isEmpty(); } @Overridepublic boolean contains(Object o) {return list.contains(0); } @Overridepublic Iterator iterator() {return list.iterator(); } @Overridepublic Object[] toArray() {return list.toArray(); } @Overridepublic T[] toArray(T[] a) {return list.toArray(a); } @Overridepublic boolean add(E e) {return list.add(e); } @Overridepublic boolean remove(Object o) {return list.remove(o); } @Overridepublic boolean containsAll(Collection> c) {return list.containsAll(c); } @Overridepublic boolean addAll(Collection extends E> c) {return list.addAll(c); } @Overridepublic boolean addAll(int index, Collection extends E> c) {return list.addAll(index, c); } @Overridepublic boolean removeAll(Collection> c) {return list.removeAll(c); } @Overridepublic boolean retainAll(Collection> c) {return list.retainAll(c); } @Overridepublic void clear() {list.clear(); } @Overridepublic E get(int index) {return list.get(index); } @Overridepublic E set(int index, E element) {return list.set(index, element); } @Overridepublic void add(int index, E element) {list.add(index, element); } @Overridepublic E remove(int index) {return list.remove(index); } @Overridepublic int indexOf(Object o) {return list.indexOf(o); } @Overridepublic int lastIndexOf(Object o) {return list.lastIndexOf(o); } @Overridepublic ListIterator listIterator() {return list.listIterator(); } @Overridepublic ListIterator listIterator(int index) {return list.listIterator(index); } @Overridepublic List subList(int fromIndex, int toIndex) {return list.subList(fromIndex, toIndex); }}
这样前端的代码是不需要任何改动的!
这个时候有小伙伴说,需要捕获异常,是的,@Validated是通过抛出异常来进行返回的。
上代码,全局异常处理:
/** * @ClassName GlobalException * @Description 异常处理拦截器 * @Author TY * @Date 13:59$ 2019-01-30$ * @Version 1.0 **/@RestControllerAdvicepublic class GlobalException {private Logger logger = LoggerFactory.getLogger(this.getClass()); /*** 参数为空异常处理** @param ex ex* @return ReturnResult*/@ExceptionHandler({ParamNullException.class, MethodArgumentNotValidException.class,ConstraintViolationException.class, HttpMessageNotReadableException.class,MissingServletRequestParameterException.class})public ReturnResult> requestMissingServletRequest(Exception ex) {ReturnResult> returnResult = new ReturnResult<>(); returnResult.setRetCode(2); if (ex instanceof MissingServletRequestParameterException) {returnResult.setRetMsg("参数非法:" + ((MissingServletRequestParameterException) ex).getParameterName()); } else if (ex instanceof HttpMessageNotReadableException) {//排除入参问题returnResult.setRetMsg("参数非法:" + ex.getMessage()); } else if (ex instanceof MethodArgumentNotValidException) {//排除入参问题FieldError error = (FieldError) ((MethodArgumentNotValidException) ex).getBindingResult().getAllErrors().get(0); returnResult.setRetMsg("参数非法:" + error.getField() + " " + error.getDefaultMessage()); } else if (ex instanceof ConstraintViolationException) {//排除入参问题ConstraintViolation> violation = ((ConstraintViolationException) ex).getConstraintViolations().iterator().next(); returnResult.setRetMsg("参数非法:" + violation.getPropertyPath().toString().split("[.]")[1]+ violation.getMessage()); }return returnResult; } /*** 其他异常** @param request request* @param exex* @return ReturnResult*/@ExceptionHandler(Exception.class)public ReturnResult> resolveException(HttpServletRequest request, Exception ex) {ReturnResult> returnResult = new ReturnResult<>(); logger.error("==============异常开始============="); logger.error("url: " + request.getRequestURL() + " msg: " + ex.getMessage()); ex.printStackTrace(); logger.info("==============异常结束============="); returnResult.setRetCode(1); returnResult.setRetMsg("GlobalException!!!"); returnResult.setRetData(ex.toString()); return returnResult; }}
ReturnResult是自己定一的方法返回封装类,根据自己的项目自己封装就行。
【@Valid|@Valid 无法校验List的问题】以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
推荐阅读
- 解决SpringBoot引用别的模块无法注入的问题
- Jsr303做前端数据校验
- Android7.0|Android7.0 第三方应用无法访问私有库
- 无论你是谁,都有两件事,无法掌控
- 没有思考时
- 2021.4.8日《我们为什么无法摆脱慢性疾病》常斌
- 插件化无法获取或找到.so文件
- springboot中.yml文件的值无法读取的问题及解决
- 2018-10-29|2018-10-29 无法靠近
- mysql提示无法找到句饼_找不到数据库启动句柄