Java Controller实现参数验证与统一异常处理流程详细讲解

作者:李奈?-?Leemon 时间:2022-01-25 18:49:47 

最近开发了比较多的接口,因为没有可参考的案例,所以一开始一直按照我的理解进行开发。开发多了发现自己每个结果都写了相同的代码:try() {} catch() {}, 和关于参数判空的:StringUtils.empty(xxx)。开发结束后自然想下次更加优雅的开发。因此,使用了springboot的参数验证和统一异常处理。

一,前期数据及类准备

1.1 统一状态码

对于不同的返回类型,我们应该要有不同对应的状态码。接口的返回类型在统一状态码中必须存在。

package com.lmc.common.enums;
/**
* @Description: TODO 接口API返回状态码枚举
* @version: 1.0
*/
public enum ResultCodeEnum {
   SUCCESS(1000, "请求成功"),
   FAILURE(1001, "请求失败"),
   VALIDATE_PARAMS_ERROR(1002, "参数校验失败");
   private int code;
   private String msg;
   ResultCodeEnum(int code, String msg) {
       this.code = code;
       this.msg = msg;
   }
   /**
    * 获取code
    * @return
    */
   public int getCode() {
       return code;
   }
   /**
    * 获取信息
    * @return
    */
   public String getMsg() {
       return msg;
   }
}

1.2 统一返回格式

统一状态码完成后,还需要定义统一返回格式,为了前端的方便调用

package com.lmc.common.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.lmc.common.enums.ResultCodeEnum;
import lombok.Data;
import java.util.Date;
/**
* @Description: TODO 接口返回结果类型
* @version: 1.0
*/
@Data
public class ResultVo {
   /**
    * 状态码
    */
   private int code;
   /**
    * 状态码信息
    */
   private String msg;
   /**
    * 返回描述信息(预备为调用失败的情况下提供详细的失败原因)
    */
   private String desc;
   /**
    * 返回数据
    */
   private Object data;
   /**
    * 接口调用结束时间
    */
   @JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")
   private Date searchTime;
   public ResultVo(int code, String msg, Object data) {
       this.code = code;
       this.msg = msg;
       this.data = data;
       this.searchTime = new Date();
   }
   /**
    * 调用成功时返回
    * @param data
    * @return
    */
   public static ResultVo success(Object data) {
       return new ResultVo(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getMsg(), data);
   }
   /**
    * 调用失败时返回
    * @param data
    * @return
    */
   public static ResultVo fail(Object data) {
       return new ResultVo(ResultCodeEnum.FAILURE.getCode(), ResultCodeEnum.FAILURE.getMsg(), data);
   }
   /**
    * 调用时指定状态码
    * @param enums
    * @param data
    * @return
    */
   public static ResultVo result(ResultCodeEnum enums, Object data) {
       return new ResultVo(enums.getCode(), enums.getMsg(), data);
   }
   public ResultVo withDesc(String desc) {
       this.desc = desc;
       return this;
   }
}

1.3 自定义接口API异常类

然后再自定义接口的异常类,当然也可以不用,看个人喜好

package pers.lmc.tools2.provider.exception;
import com.lmc.common.enums.ResultCodeEnum;
/**
* @Description: TODO API异常类
* @version: 1.0
*/
public class ApiException extends RuntimeException{
   private int code;
   private String msg;
   public ApiException(String msg) {
       super(msg);
       this.code = ResultCodeEnum.FAILURE.getCode();
       this.msg = ResultCodeEnum.FAILURE.getMsg();
   }
   public ApiException(ResultCodeEnum enums, String msg) {
       super(msg);
       this.code = enums.getCode();
       this.msg = enums.getMsg();
   }
}

1.4 参数封装类

为了调试参数验证,还需要自定义一个参数的封装类

package pers.lmc.tools2.provider.vo;
import lombok.Data;
/**
* @Description: TODO
* @version: 1.0
*/
@Data
public class Param01Vo {
   private String name;
   private Integer age;
   private Short sex;
}

二,参数验证

参数验证需要用到springboot的validation依赖

2.1 pom.xml

<dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-validation</artifactId>
       </dependency>
<!--   关于校验     -->
       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-databind</artifactId>
           <version>${jackson.version}</version>
       </dependency>
       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-core</artifactId>
           <version>${jackson.version}</version>
       </dependency>
       <dependency>
           <groupId>com.fasterxml.jackson.core</groupId>
           <artifactId>jackson-annotations</artifactId>
           <version>${jackson.version}</version>
       </dependency>

2.2 修改参数封装类

package pers.lmc.tools2.provider.vo;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* @Description: TODO
* @version: 1.0
*/
@Data
public class Param01Vo {
   @NotNull(message = "名称不能为空")
   @Size(min = 1, max = 50, message = "名称name长度必须是1-50个字符")
   private String name;
   @NotNull(message = "年龄age不能为空")
   @Min(value = 10, message = "年龄age不能低于10岁")
   @Max(value = 25, message = "年龄age不能超过25岁")
   private Integer age;
   @Min(value = 0, message = "性别sex只能是0和1,0=女1=男")
   @Max(value = 1, message = "性别sex只能是0和1,0=女1=男")
   private Short sex;
}

在这里对该封装类的三个参数都做了限制

2.3 controller

在controller中对参数做验证时,需要在类上使用注解@Validated,同时在接口的该参数也使用注解@Valid

package pers.lmc.tools2.provider.controller;
import com.lmc.common.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.lmc.tools2.provider.vo.Param01Vo;
import javax.validation.Valid;
/*
* @Description: TODO
* @version: 1.0
*/
@RestController
@Validated
@RequestMapping("/valicate")
@Slf4j
public class ValicateController {
   @PostMapping("/add")
   public ResultVo addParam01(@Valid @RequestBody Param01Vo param01Vo) {
       log.info("执行add()方法,参数:" + param01Vo.toString());
       return ResultVo.success(param01Vo);
   }
}

2.4 测试

开发完成,准备测试,到APIPost上 访问 http://localhost:9003/provider/valicate/add,带上参数:

{
   "name":"lmc",
   "age": 22,
   "sex": 1
}

访问成功,返回结果如下:

{
"code": 1000,
"msg": "请求成功",
"desc": null,
"data": {
"name": "lmc",
"age": 22,
"sex": 1
},
"searchTime": "2022-06-26 19:59:55"
}

如果参数输入不正确,例如:

{
   "name":"",
   "age": 220,
   "sex": 2
}

得到结果如下:

{
"timestamp": "2022-06-26T12:02:21.748+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/provider/valicate/add"
}

日志是这样的:

2022-06-26 20:02:21 [http-nio-9003-exec-1] WARN  o.s.w.s.m.support.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.lmc.common.vo.ResultVo pers.lmc.tools2.provider.controller.ValicateController.addParam01(pers.lmc.tools2.provider.vo.Param01Vo) with 3 errors: [Field error in object 'param01Vo' on field 'sex': rejected value [2]; codes [Max.param01Vo.sex,Max.sex,Max.java.lang.Short,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.sex,sex]; arguments []; default message [sex],1]; default message [性别sex只能是0和1,0=女1=男]] [Field error in object 'param01Vo' on field 'age': rejected value [220]; codes [Max.param01Vo.age,Max.age,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.age,age]; arguments []; default message [age],25]; default message [年龄age不能超过25岁]] [Field error in object 'param01Vo' on field 'name': rejected value []; codes [Size.param01Vo.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.name,name]; arguments []; default message [name],50,1]; default message [名称name长度必须是1-50个字符]] ]

抛出了MethodArgumentNotValidException异常。

虽然参数错误时确实被拦截了,但格式已经和我们想要返回的不一致了。这个时候,就需要用到统一异常处理了。

三,统一异常处理

3.1 方法参数验证异常处理

通过以上的问题,我们可以设置controller的统一异常处理,当出现参数验证错误时,就捕获MethodArgumentNotValidException异常,然后我们自己做处理。

package pers.lmc.tools2.provider.aop;
import com.lmc.common.enums.ResultCodeEnum;
import com.lmc.common.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import pers.lmc.tools2.provider.exception.ApiException;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Description: TODO
* @version: 1.0
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
   /**
    * 处理所有校验失败的异常(MethodArgumentNotValidException异常)
    * @param e
    * @return
    */
   @ExceptionHandler(value = MethodArgumentNotValidException.class)
   public ResultVo handleBindGetException(MethodArgumentNotValidException e) {
       // 获取所有异常参数
       List<String> errors = e.getBindingResult()
               .getFieldErrors()
               .stream()
               .map(x -> x.getDefaultMessage())
               .collect(Collectors.toList());
       return ResultVo.result(ResultCodeEnum.VALIDATE_PARAMS_ERROR, null).withDesc("参数校验失败:" + errors);
   }
   /**
    * 处理自定义APIException异常
    * @param e
    * @return
    */
   @ExceptionHandler(value = ApiException.class)
   public ResultVo handleApiException(ApiException e) {
       return ResultVo.fail(null).withDesc(e.getMessage());
   }
   /**
    * 处理其他异常
    * @param e
    * @return
    */
   @ExceptionHandler(value = Exception.class)
   public ResultVo handleException(Exception e) {
       log.info("执行到统一处理方法...");
       return ResultVo.fail(null).withDesc(e.getMessage());
   }
}

通过以上配置,再次以非法参数传输时,会报出以下错误:

{
"code": 1002,
"msg": "参数校验失败",
"desc": "参数校验失败:[性别sex只能是0和1,0=女1=男, 名称name长度必须是1-50个字符, 年龄age不能超过25岁]",
"data": null,
"searchTime": "2022-06-26 20:08:22"
}

这个时候格式已经我们想要的返回格式了。

3.2 其他异常处理

刚刚我们尝试的是方法的参数验证异常的处理,对于程序还可能出现的错误,配置统一异常处理后也不需要使用try{} catch() {},因为我们已经在全局异常处理类中配置了:

/**
    * 处理其他异常
    * @param e
    * @return
    */
   @ExceptionHandler(value = Exception.class)
   public ResultVo handleException(Exception e) {
       log.info("执行到统一处理方法...");
       return ResultVo.fail(null).withDesc(e.getMessage());
   }

这个时候在程序中抛出其他异常,就会执行到这里的代码,同样返回我们想要的格式。举例如下

修改controller接口:

@PostMapping("/add")
   public ResultVo addParam01(@Valid @RequestBody Param01Vo param01Vo) {
       log.info("执行add()方法,参数:" + param01Vo.toString());
       int k = 1/0; // 调用该接口时执行到这里会抛出异常
       return ResultVo.success(param01Vo);
   }

调用接口返回结果:

{
"code": 1001,
"msg": "请求失败",
"desc": "/ by zero",
"data": null,
"searchTime": "2022-06-26 20:13:51"
}

来源:https://blog.csdn.net/lmchhh/article/details/125473736?spm=1001.2014.3001.5502

标签:Java,Controller,参数验证,异常处理
0
投稿

猜你喜欢

  • SpringBoot+Vue项目新手快速入门指南

    2023-05-20 04:56:07
  • java递归算法实例分析

    2022-02-09 07:59:17
  • Kotlin基础教程之控制流(顺序,分支,循环)

    2022-03-15 11:37:33
  • 浅析SpringBoot2.4 静态资源加载问题

    2023-01-29 11:04:33
  • Android 获取判断是否有悬浮窗权限的方法

    2023-11-04 22:48:18
  • Android平台预置GMS包后关机闹钟失效问题及解决方法

    2022-12-31 05:52:33
  • c# AcceptEx与完成端口(IOCP)结合的示例

    2023-07-29 01:46:24
  • C#图片查看器实现方法

    2021-06-17 16:50:03
  • Android 实现全屏显示的几种方法整理

    2022-05-09 03:21:01
  • SpringMVC中使用@PathVariable绑定路由中的数组的方法

    2023-11-27 14:21:01
  • Java 实战项目之CRM客户管理系统的实现流程

    2022-12-01 22:50:54
  • Netty分布式Future与Promise执行回调相关逻辑剖析

    2021-08-18 23:33:50
  • C#11新特性使用案例详解

    2023-11-26 03:19:15
  • JAVA JDK8 List分组获取第一个元素的方法

    2021-06-24 13:07:51
  • Java使用synchronized修饰方法来同步线程的实例演示

    2022-11-29 18:33:29
  • Android实现背景图滑动变大松开回弹效果

    2022-10-15 10:45:15
  • C#检测是否有危险字符的SQL字符串过滤方法

    2023-10-11 12:48:48
  • Java如何使用Optional与Stream取代if判空逻辑(JDK8以上)

    2022-01-15 02:52:53
  • android实现双日期选择控件(可隐藏日,只显示年月)

    2023-05-09 12:59:10
  • Maven本地打包war包实现代码解析

    2021-09-06 12:51:14
  • asp之家 软件编程 m.aspxhome.com