<
>

@validated注解异常返回JSON值方式

2022-04-15 07:08:42 来源:易采站长站 作者:

目录
@validated注解异常返回jsON值使用@Valid注解,对其参数错误异常的统一处理

@validated注解异常返回JSON值

@ControllerAdvicepublic class ValidParamExceptionHandler {    @ExceptionHandler(value = Exception.class)    @ResponseBody    public Map<String, Object> allExceptionHandler(Exception e){         Map<String, Object> map = new HashMap<>(2);         if(e instanceof BindException) {            BindException ex = (BindException)e;            BindingResult bindingResult = ex.getBindingResult();            StringBuilder errMsg = new StringBuilder(bindingResult.getFieldErrors().size() * 16);            errMsg.append("Invalid request:");            for (int i = 0 ; i < bindingResult.getFieldErrors().size() ; i++) {                if(i > 0) {                    errMsg.append(",");                }                FieldError error = bindingResult.getFieldErrors().get(i);                errMsg.append(error.getField()+":"+error.getDefaultMessage());            }            map.put("errcode", 500);            map.put("errmsg", errMsg.toString());                }        else {                map.put("errcode", 500);            map.put("errmsg", e.getMessage());        }        return map;    }

(1)这里@ControllerAdvice注解标注,@ControllerAdvice是@Controller的增强版,一般与@ExceptionHandler搭配使用。

如果标注@Controller,异常处理只会在当前controller类中的方法起作用,但是使用@ControllerAdvice,则全局有效。

(2)@ExceptionHandler注解里面填写想要捕获的异常类class对象

使用@Valid注解,对其参数错误异常的统一处理

在我们使用springboot作为微服务框架进行敏捷开发的时候,为了保证传递数据的安全性,需要对传递的数据进行校验,但是在以往的开发中,开发人员花费大量的时间在繁琐的if else 等判断语句来对参数进行校验,这种方式不但降低了我们的开发速度,而且写出来的代码中带有很多冗余代码,使得编写的代码不够优雅,为了将参数的验证逻辑和代码的业务逻辑进行解耦,java给我们提供了@Valid注解,用来帮助我们进行参数的校验,实现了将业务逻辑和参数校验逻辑在一定程度上的解耦,增加了代码的简洁性和可读性。

springboot中自带了spring validation参数校验框架,其使用上和@valid差不多,在这里就不累述了,本文主要讲解@valid的使用对其参数校验失败后的错误一样的统一处理。

首先,简介对微服务开发中异常的统一处理,spring中的@RestControllerAdvice注解可以获取带有@controller注解类的异常,通过@ExceptionHandler(MyException.class)注解来共同完成对异常进行处理。示例如下:

/** * 通用异常拦截处理类(通过切面的方式默认拦截所有的controller异常) */@Slf4j@RestControllerAdvicepublic class CommonExceptionHandler {     /**     * 对运行时异常进行统一异常管理方法     * @param e     * @return     */    @ExceptionHandler(FlyException.class) // FlyException类继承于RuntimeException    public ResponseEntity<Map<String, Object>> handlerException(FlyException e) {        Map<lidateIfApplicable(binder, parameter);                // 如果获取参数绑定的结果中包含错误的信息则抛出异常    if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {     throw new BindException(binder.getBindingResult());    }   }   // Value type adaptation, also covering java.util.Optional   if (!parameter.getParameterType().isInstance(attribute)) {    attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);   }   bindingResult = binder.getBindingResult();  }   // Add resolved attribute and BindingResult at the end of the model  Map<String, Object> bindingResultModel = bindingResult.getModel();  mavContainer.removeAttributes(bindingResultModel);  mavContainewww.easck.comr.addAllAttributes(bindingResultModel);   return attribute;}

通过查看源码,当BindingResult中存在错误信息时,会抛出BindException异常,查看BindException源代码如下:

/** * Thrown when binding errors are considered fatal. Implements the * {@link BindingResult} interface (and its super-interface {@link Errors}) * to allow for the direct analysis of binding errors. * * <p>As of Spring 2.0, this is a special-purpose class. Normally, * application code will work with the {@link BindingResult} interface, * or with a {@link DataBinder} that in turn exposes a BindingResult via * {@link org.springframework.validation.DataBinder#getBindingResult()}. * * @author Rod Johnson * @author Juergen Hoeller * @author Rob Harrop * @see BindingResult * @see DataBinder#getBindingResult() * @see DataBinder#close() */@SuppressWarnings("serial")public class BindException extends Exception implements BindingResult {  private final BindingResult bindingResult;   /**  * Create a new BindException instance for a BindingResult.  * @param bindingResult the BindingResult instance to wrap  */ public BindException(BindingResult bindingResult) {  Assert.notNull(bindingResult, "BindingResult must not be null");  this.bindingResult = bindingResult; }

我们发现BindException实现了BindingResult接口(BindResult是绑定结果的通用接口, BindResult继承于Errors接口),所以该异常类拥有BindingResult所有的相关信息,因此我们可以通过捕获该异常类,对其错误结果进行分析和处理。代码如下:

/** * 对方法参数校验异常处理方法 */@ExceptionHandler(BindException.class)public ResponseEntity<Map<String, Object>> handlerNotValidException(BindException exception) {    log.debug("begin resolve argument exception");    BindingResult result = exception.getBindingResult();    Map<String, Object> maps;     if (result.hasErrors()) {        List<FieldError> fieldErrors = result.getFieldErrors();        maps = new HashMap<>(fieldErrors.size());        fieldErrors.forEach(error -> {            maps.put(error.getField(), error.getDefaultMessage());        });    } else {        maps = Collections.EMPTY_MAP;    }     return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(maps);}

这样,我们对是content-type类型为form(表单)类型的请求的参数校验的异常处理就解决了,对于MethodArgumentNotValidException异常不起作用的原因主要是因为跟请求发起的数据格式(content-type)有关系,对于不同的传输数据的格式spring采用不同的HttpMessageConverter(http参数转换器)来进行处理.以下是对HttpMessageConverter进行简介:

HTTP 请求和响应的传输是字节流,意味着浏览器和服务器通过字节流进行通信。但是,使用 Spring,controller 类中的方法返回纯 String 类型或其他 Java 内建对象。如何将对象转换成字节流进行传输?

在报文到达SpringMVC和从SpringMVC出去,都存在一个字节流到java对象的转换问题。在SpringMVC中,它是由HttpMessageConverter来处理的。

当请求报文来到java中,它会被封装成为一个ServletInputStream的输入流,供我们读取报文。响应报文则是通过一个ServletOutputStream的输出流,来输出响应报文。http请求与相应的处理过程如下:

针对不同的数据格式,springmvc会采用不同的消息转换器进行处理,以下是springmvc的内置消息转换器:

由此我们可以看出,当使用json作为传输格式时,springmvc会采用MappingJacksonHttpMessageConverter消息转换器, 而且底层在对参数进行校验错误时,抛出的是MethodArgumentNotValidException异常,因此我们需要对BindException和MethodArgumentNotValidException进行统一异常管理,最终代码演示如下所示:

/**     * 对方法参数校验异常处理方法(仅对于表单提交有效,对于以json格式提交将会失效)     * 如果是表单类型的提交,则spring会采用表单数据的处理类进行处理(进行参数校验错误时会抛出BindException异常)     */    @ExceptionHandler(BindException.class)    public ResponseEntity<Map<String, Object>> handlerBindException(BindException exception) {        return handlerNotValidException(exception);    }     /**     * 对方法参数校验异常处理方法(前端提交的方式为json格式出现异常时会被该异常类处理)     * json格式提交时,spring会采用json数据的数据转换器进行处理(进行参数校验时错误是抛出MethodArgumentNotValidException异常)     */    @ExceptionHandler(MethodArgumentNotValidException.class)    public ResponseEntity<Map<String, Object>> handlerArgumentNotValidException(MethodArgumentNotValidException exception) {        return handlerNotValidException(exception);    }     public ResponseEntity<Map<String, Object>> handlerNotValidException(Exception e) {        log.debug("begin resolve argument exception");        BindingResult result;        if (e instanceof BindException) {            BindException exception = (BindException) e;            result = exceptionmIsSAsz.getBindingResult();        } else {            MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e;            result = exception.getBindingResult();        }         Map<String, Object> maps;        if (result.hasErrors()) {            List<FieldError> fieldErrors = result.getFieldErrors();            maps = new HashMap<>(fieldErrors.size());            fieldErrors.forEach(error -> {                maps.put(error.getField(), error.getDefaultMessage());            });        } else {            maps = Collections.EMPTY_MAP;        }         return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(maps);     }

这样就完美解决了我们对参数校验异常的统一处理。

在这里我仅仅是针对参数校验的异常进行了统一处理,也就是返回给前端的响应码是400(参数格式错误),对于自定义异常或者其他的异常都可以采用这种方式来对异常进行统一处理。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

暂时禁止评论

微信扫一扫

易采站长站微信账号