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

一,前期数据及类准备
1.1 统一状态码
对于不同的返回类型,我们应该要有不同对应的状态码。接口的返回类型在统一状态码中必须存在。

package com.lmc.common.enums;/*** @author lmc* @Description: TODO 接口API返回状态码枚举* @Create 2022-06-26 13:16* @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;/*** @author lmc* @Description: TODO 接口返回结果类型* @Create 2022-06-26 13:13* @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;/*** @author lmc* @Description: TODO API异常类* @Create 2022-06-26 18:48* @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;/*** @author lmc* @Description: TODO* @Create 2022-06-26 13:43* @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;/*** @author lmc* @Description: TODO* @Create 2022-06-26 13:43* @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;/*** @author lmc* @Description: TODO* @Create 2022-06-26 17:10* @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;/*** @author lmc* @Description: TODO* @Create 2022-06-26 18:31* @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"
}

优雅书写Controller(参数验证+统一异常处理)相关推荐

  1. 使用优雅方式对参数验证进行处理

    我们在一般的接口函数开发中,为了安全性,我们都需要对传入的参数进行验证,确保参数按照我们所希望的范围输入,如果在范围之外,如空值,不符合的类型等等,都应该给出异常或错误提示信息.这个参数的验证处理有多 ...

  2. validation 参数校验和统一异常处理

    文章目录 1. 引入依赖 2. 校验规则 3. 规则使用 4. 自定义异常类 5. 统一返回对象封装 6. 统一异常对象 7. 统一异常枚举 8. 前端form表单输入,自动触发校验 1. 引入依赖 ...

  3. controller方法要trycatch吗_拜托,别再满屏try catch了,试试统一异常处理吧

    点击蓝色"JavaKeeper"关注我哟 加个"星标",一起成长,做牛逼闪闪的技术人 https://sourl.cn/SLnSKu 背景 软件开发过程中,不可 ...

  4. Spring中Controller层、Filter层、Interceptor层全局统一异常处理

    Controller层.Filter层.Interceptor层全局统一异常处理 SpringBoot为异常处理提供了很多优秀的方法,但是像我这种新手在处理异常时还是会觉得一头包,终于我痛定思痛,总结 ...

  5. ssm 异常捕获 统一处理_SpringMVC 统一异常处理介绍及实战

    背景 什么是统一异常处理 目标 统一异常处理实战 用 Assert(断言) 替换 throw exception 定义统一异常处理器类 扩展 总结 <Java 2019 超神之路> < ...

  6. trycatch抛出异常_Java生鲜电商平台架构中,如何统一异常处理及架构实战

    补充说明:本文讲得比较细,所以篇幅较长.请认真读完,希望读完后能对统一异常处理有一个清晰的认识. 背景 软件开发过程中,不可避免的是需要处理各种异常,就我自己来说,至少有一半以上的时间都是在处理各种异 ...

  7. ssm 异常捕获 统一处理_统一异常处理介绍及实战

    作者:sprinkle_lizhttp://www.jianshu.com/p/3f3d9e8d1efa 推荐阅读(点击即可跳转阅读) 1. SpringBoot内容聚合 2. 面试题内容聚合 3. ...

  8. Java生鲜电商平台-统一异常处理及架构实战

    Java生鲜电商平台-统一异常处理及架构实战 补充说明:本文讲得比较细,所以篇幅较长. 请认真读完,希望读完后能对统一异常处理有一个清晰的认识. 背景 软件开发过程中,不可避免的是需要处理各种异常,就 ...

  9. illegalargumentexception是什么异常_实战 | 统一异常处理介绍及实战

    背景 软件开发过程中,不可避免的是需要处理各种异常,就我自己来说,至少有一半以上的时间都是在处理各种异常情况,所以代码中就会出现大量的try {...} catch {...} finally {.. ...

最新文章

  1. Python——Entry、Text控件
  2. 异步、作用域、闭包--setTimeout在for循环中的思考
  3. OCP换题库了,052新加的考题及答案整理-第16题
  4. java nextday_Nextday 参数化单元测试(测试用例)设计
  5. 【转】asp.net中的WebApplication(web应用程序)和WebSite(网站)
  6. Primefaces dataTable设置某个cell的样式问题
  7. MySQL数据库数据分开存储
  8. C语言中的常用文件操作
  9. [转载] Python基础之类型转换与算术运算符
  10. python如何遍历二维数组的列元素_for循环获取二维数组的元素时的bug
  11. swagger php修改成中文,PHP使用swagger自动生成API文档
  12. 【CodeForces - 471D 】【构造差分kmp】MUH and Cube Walls
  13. 由陌生到认识——物联网LoRa技术入门简介
  14. 如何用python计算年龄_python根据出生日期计算年龄的代码
  15. formidable词根词缀_SAT词根词缀汇总内容(6)
  16. ElasticSearch:简单介绍以及使用Docker部署ElasticSearch 和 Kibana
  17. 让技术Leader疯狂点赞的Linux速成手册,到底有多强悍?
  18. np.piecewise函数用法
  19. 在Google 上搜书的方法
  20. 2018年网络安全大事记

热门文章

  1. 3D Bounding Box Estimation Using Deep Learning and Geometry 论文笔记
  2. 1254计算机组成原理试题,国家开放大学电大本科《计算机组成原理》2021期末试题及答案(1254)...
  3. mysql数据库创建一个点餐系统用户表
  4. win10安装 graph_tool工具箱
  5. 面试题:15个人围成圈数数 每逢报数为七的倍数就退出,一直循环下去 ,直到剩下最后一人 ,最后剩余的是第几个人,展示出退出的顺序
  6. 瑞萨单片机CAN口复用唤醒
  7. 奈雪的茶怎么不排队了?
  8. 交易策略理想与现实之间的距离之流动性
  9. c语言精粹,C语言精粹.pdf
  10. php语言精髓,PHP语言精粹