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

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

@validated注解异常返回JSON值

@ControllerAdvice
public 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
@RestControllerAdvice
public class CommonExceptionHandler {

    /**
     * 对运行时异常进行统一异常管理方法
     * @param e
     * @return
     */
    @ExceptionHandler(FlyException.class) // FlyException类继承于RuntimeException
    public ResponseEntity<Map<String, Object>> handlerException(FlyException e) {
        Map<String, Object> result = new HashMap<>(1);
        result.put("message", e.getMessage());
        return ResponseEntity.status(e.getCode()).body(result);
    }

通过注解@RestControllerAdvice和注解@ExceptionHandler的联合使用来实现对异常的统一处理,然后在前端以友好的方式显示。

使用@Valid注解的示例如下:

  @PostMapping
    public ResponseEntity save(@Valid BrandCreateRequestDto dto, BindingResult bindingResult) {
        // 判断是否含有校验不匹配的参数错误
        if (bindingResult.hasErrors()) {
            // 获取所有字段参数不匹配的参数集合
            List<FieldError> fieldErrorList = bindingResult.getFieldErrors();
            Map<String, Object> result = new HashMap<>(fieldErrorList.size());
            fieldErrorList.forEach(error -> {
                // 将错误参数名称和参数错误原因存于map集合中
                result.put(error.getField(), error.getDefaultMessage());
            });
            return ResponseEntity.status(HttpStatus.BAD_REQUEST.value()).body(result);
        }
        brandService.save(dto);
        return ResponseEntity.status(HttpStatus.CREATED.value()).build();
    }

@Valid注解确实将我们原来的参数校验的问题进行了简化,但是,如果我们有多个handler需要处理,那我们岂不是每次都要写这样的冗余代码。通过查看@valid的实现机制(这里就不描述了),当参数校验失败的时候,会抛出MethodArgumentNotValidException异常(当用{@code @Valid}注释的参数在验证失败时,将引发该异常):

/**
 * Exception to be thrown when validation on an argument annotated with {@code @Valid} fails.
 *
 * @author Rossen Stoyanchev
 * @since 3.1
 */
@SuppressWarnings("serial")
public class MethodArgumentNotValidException extends Exception {
 private final MethodParameter parameter;
 private final BindingResult bindingResult; 

 /**
  * Constructor for {@link MethodArgumentNotValidException}.
  * @param parameter the parameter that failed validation
  * @param bindingResult the results of the validation
  */
 public MethodArgumentNotValidException(MethodParameter parameter, BindingResult bindingResult) {
  this.parameter = parameter;
  this.bindingResult = bindingResult;
 }

按照我们的预想,我们只需要在原来定义的统一异常处理类中,捕获MethodArgumentNotValidException异常,然后对其错误信息进行分析和处理即可实现通用,代码如下:

/**
 * 对方法参数校验异常处理方法
 */
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handlerNotValidException(MethodArgumentNotValidException 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);
}

但是经过测试,会发现对该异常进行捕获然后处理是没有效果的,这可能是我,也是大家遇到的问题之一,经过对@Valid的执行过程的源码进行分析,数据传递到spring中的执行过程大致为:前端通过http协议将数据传递到spring,spring通过HttpMessageConverter类将流数据转换成Map类型,然后通过ModelAttributeMethodProcessor类对参数进行绑定到方法对象中,并对带有@Valid或@Validated注解的参数进行参数校验,对参数进行处理和校验的方法为ModelAttributeMethodProcessor.resolveArgument(...),部分源代码如下所示:

public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

...
 public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
   NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        ...
        Object attribute = null;
  BindingResult bindingResult = null;

  if (mavContainer.containsAttribute(name)) {
   attribute = mavContainer.getModel().get(name);
  }
  else {
   // Create attribute instance
   try {
    attribute = createAttribute(name, parameter, binderFactory, webRequest);
   }
   catch (BindException ex) {
    if (isBindExceptionRequired(parameter)) {
     // No BindingResult parameter -> fail with BindException
     throw ex;
    }
    // Otherwise, expose null/empty value and associated BindingResult
    if (parameter.getParameterType() == Optional.class) {
     attribute = Optional.empty();
    }
    bindingResult = ex.getBindingResult();
   }
  }
        //进行参数绑定和校验
  if (bindingResult == null) {
   // 对属性对象的绑定和数据校验;
   // 使用构造器绑定属性失败时跳过.
   WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);

   if (binder.getTarget() != null) {
    if (!mavContainer.isBindingDisabled(name)) {
     bindRequestParameters(binder, webRequest);
    }
                // 对绑定参数进行校验,校验失败,将其结果信息赋予bindingResult对象
    validateIfApplicable(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);
  mavContainer.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 = exception.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(参数格式错误),对于自定义异常或者其他的异常都可以采用这种方式来对异常进行统一处理。

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

(0)

相关推荐

  • 浅谈spring方法级参数校验(@Validated)

    依赖的jar包: spring相关jar包版本:4.3.1.RELEASE <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency> 一.配置与注入 MethodValidationPostProce

  • spring 注解验证@NotNull等使用方法

    本文介绍了spring 注解验证@NotNull等使用方法,分享给大家,具体如下: 常用标签 @Null  被注释的元素必须为null @NotNull  被注释的元素不能为null @AssertTrue  被注释的元素必须为true @AssertFalse  被注释的元素必须为false @Min(value)  被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max(value)  被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @DecimalMin(value)

  • 一次踩坑记录 @valid注解不生效 排查过程

    一.背景 在进行一次Controller层单测时,方法参数违反Validation约束,发现却没有抛出预期的[违反约束]异常. 方法参数上的@Valid注解不生效?? 但是以Tomcatweb容器方式启动,请求该API,@Valid注解却生效了,甚是怪异. 代码如下: @RestController @RequestMapping("/api/user/") public class UserController @RequestMapping(value = ""

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

    目录 @validated注解异常返回JSON值 使用@Valid注解,对其参数错误异常的统一处理 @validated注解异常返回JSON值 @ControllerAdvice public class ValidParamExceptionHandler { @ExceptionHandler(value = Exception.class) @ResponseBody public Map<String, Object> allExceptionHandler(Exception e){

  • laravel框架使用FormRequest进行表单验证,验证异常返回JSON操作示例

    本文实例讲述了laravel框架使用FormRequest进行表单验证,验证异常返回JSON操作.分享给大家供大家参考,具体如下: 通常在项目中,我们会对大量的前端提交过来的表单进行验证,如果不通过,则返回错误信息. 前端为了更好的体验,都使用ajax进行表单提交,虽然 validate() 方法能够根据前端的不同请求方式,返回不同的结果. 但是返回的json格式并不是我们想要的,这个时候,我们就需要自定义返回错误结果. 先创建一个表单请求类: php artisan make:request

  • ajax后台处理返回json值示例代码

    复制代码 代码如下: public ActionForward xsearch(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String parentId = request.getParameter("parentId"); String supplier = request.getParamet

  • Mongoose find 查询返回json数据处理方式

    目录 前言 需求 处理思路 遇到问题 解决方案 总结 前言 Mongoose find方法,打印看着返回的是json数据,实际返回的是Mongoose实例,为了方便自定义拓展或操作链式操作. 需求 如图复制按钮,点击复制按钮填写信息,复制出有相同属性的数据模型: 处理思路 传参:{id:"", //被复制的数据模型id ...(其他填写参数) };通过id查询被复制数据模型所有数据,删除数据id,删除属性id,其他填写参数覆盖,然后写库. 遇到问题 代码如下,执行时,直接报堆栈溢出,获

  • 详解Spring MVC3返回JSON数据中文乱码问题解决

    查了下网上的一些资料,感觉比较复杂,这里,我这几使用两种很简单的办法解决了中文乱码问题. Spring版本:3.2.2.RELEASE Jackson JSON版本:2.1.3 解决思路:Controller的方法中直接通过response向网络流写入String类型的json数据. 使用 Jackson 的 ObjectMapper 将Java对象转换为String类型的JSON数据. 为了避免中文乱码,需要设置字符编码格式,例如:UTF-8.GBK 等. 代码如下: import org.s

  • springboot 返回json格式数据时间格式配置方式

    目录 返回json格式数据时间格式配置 返回JSON日期格式问题 返回json格式数据时间格式配置 数据库里面查出来的时间是时间错格式,前段需要处理才能展示相应的格式,自己一个个转的话太麻烦,所以可以在apllication.property加入下面配置就可以 #时间戳统一转换 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8 其中time-zone是时区偏移设置,如果不指定的话时间和北京时

  • SpringMVC返回json数据的三种方式

    Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面.Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块.使用 Spring 可插入的 MVC架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的SpringMVC框架或集成其他MVC开发框架,如Struts1,Struts2等. 1.第一种方式是spring2时代的产物,也就是每个json视图controller配置一个Jsoniew. 如:<bean

  • Thinkphp 在api开发中异常返回依然是html的解决方式

    现在谁不开发接口的呢?但是在接口开发过程中,报错误异常后居然返回错误的信息依然是html信息!TP官方也不知道为啥不添加,说好的为接口而生,我的解决方案也很简单,把系统的异常处理类复制出来,去掉模板相关,直接以json方式输出 下面是解决方案: 1:按照TP扩展异常的方式引用这个文件 https://www.kancloud.cn/manual/thinkphp5_1/354092 // 判断默认输出类型 // $app 是配置数组 if ($app['default_return_type']

  • SpringBoot项目中处理返回json的null值(springboot项目为例)

    在后端数据接口项目开发中,经常遇到返回的数据中有null值,导致前端需要进行判断处理,否则容易出现undefined的情况,如何便捷的将null值转换为空字符串? 以SpringBoot项目为例,SSM同理. 1.新建配置类(JsonConfig.java) import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.f

  • mybatis调用mysql存储过程并获取返回值方式

    目录 mybatis调用mysql存储过程并获取返回值 1.mysql创建存储过程 2.mybatis调用 mybatis调存储过程遇到返回值null的坑 mybatis调存储过程时返回值null mybatis调用mysql存储过程并获取返回值 1.mysql创建存储过程 #结束符号默认;, delimiter $$语句表示结束符号变更为$$ delimiter $$ CREATE PROCEDURE `demo`(IN inStr VARCHAR(100), out ourStr VARCH

随机推荐