SpringBoot统一处理返回结果和异常情况
如果文章有帮助到你,还请点个赞或留下评论
原因
在springboot项目里我们希望接口返回的数据包含至少三个属性:
- code:请求接口的返回码,成功或者异常等返回编码,例如定义请求成功。
- message:请求接口的描述,也就是对返回编码的描述。
- data:请求接口成功,返回的结果。
{
"code":20000,
"message":"成功",
"data":{
"info":"测试成功"
}
}
开发环境
- 工具:IDEA
- SpringBoot版本:
2.2.2.RELEASE - 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!-- Spring web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- swagger3 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
创建 SpringBoot 工程
新建 SpringBoot 项目common_utils,包名com.spring.utils
返回结果统一
创建code枚举
在com.spring.utils中创建 pojo 包,并添加枚举ResultCode
public enum ResultCode {
/* 成功状态码 */
SUCCESS(20000, "成功"),
/* 参数错误 */
PARAM_IS_INVALID(1001, "参数无效"),
PARAM_IS_BLANK(1002, "参数为空"),
PARAM_TYPE_BIND_ERROR(1003, "参数类型错误"),
PARAM_NOT_COMPLETE(1004, "参数缺失"),
/* 用户错误 2001-2999*/
USER_NOTLOGGED_IN(2001, "用户未登录"),
USER_LOGIN_ERROR(2002, "账号不存在或密码错误"),
SYSTEM_ERROR(10000, "系统异常,请稍后重试");
private Integer code;
private String message;
private ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer code() {
return this.code;
}
public String message() {
return this.message;
}
}
- 可根据项目自定义,结果返回码
创建返回结果实体
在 pojo 包中添加返回结果类R
@Data
@ApiModel(value = "返回结果实体类", description = "结果实体类")
public class R implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private Object data;
private R() {
}
public R(ResultCode resultCode, Object data) {
this.code = resultCode.code();
this.message = resultCode.message();
this.data = data;
}
private void setResultCode(ResultCode resultCode) {
this.code = resultCode.code();
this.message = resultCode.message();
}
// 返回成功
public static R success() {
R result = new R();
result.setResultCode(ResultCode.SUCCESS);
return result;
}
// 返回成功
public static R success(Object data) {
R result = new R();
result.setResultCode(ResultCode.SUCCESS);
result.setData(data);
return result;
}
// 返回失败
public static R fail(Integer code, String message) {
R result = new R();
result.setCode(code);
result.setMessage(message);
return result;
}
// 返回失败
public static R fail(ResultCode resultCode) {
R result = new R();
result.setResultCode(resultCode);
return result;
}
}
自定义一个注解
新建包annotation,并添加ResponseResult注解类
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface ResponseResult {
}
定义拦截器
新建包interceptor,并添加ResponseResultInterceptorJava类
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
//标记名称
public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// TODO Auto-generated method stub
if (handler instanceof HandlerMethod) {
final HandlerMethod handlerMethod = (HandlerMethod) handler;
final Class<?> clazz = handlerMethod.getBeanType();
final Method method = handlerMethod.getMethod();
// 判断是否在类对象上添加了注解
if (clazz.isAnnotationPresent(ResponseResult.class)) {
// 设置此请求返回体,需要包装,往下传递,在ResponseBodyAdvice接口进行判断
request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
} else if (method.isAnnotationPresent(ResponseResult.class)) {
request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class));
}
}
return true;
}
}
- 用于拦截请求,判断 Controller 是否添加了
@ResponseResult注解
注册拦截器
新建包config,并添加WebAppConfig配置类
@Configuration
public class WebAppConfig implements WebMvcConfigurer {
// SpringMVC 需要手动添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
ResponseResultInterceptor interceptor = new ResponseResultInterceptor();
registry.addInterceptor(interceptor);
WebMvcConfigurer.super.addInterceptors(registry);
}
}
方法返回值拦截处理器
新建包handler,并添加ResponseResultHandler配置类,实现ResponseBodyAdvice重写两个方法
import org.springframework.web.method.HandlerMethod;
/**
* 使用 @ControllerAdvice & ResponseBodyAdvice
* 拦截Controller方法默认返回参数,统一处理返回值/响应体
*/
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
// 标记名称
public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
// 判断是否要执行 beforeBodyWrite 方法,true为执行,false不执行,有注解标记的时候处理返回值
@Override
public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
// 判断请求是否有包装标记
ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
return responseResultAnn == null ? false : true;
}
// 对返回值做包装处理
@Override
public Object beforeBodyWrite(Object body, MethodParameter arg1, MediaType arg2,
Class<? extends HttpMessageConverter<?>> arg3, ServerHttpRequest arg4, ServerHttpResponse arg5) {
if (body instanceof R) {
return (R) body;
} else if (body instanceof String) {
return body;
}
return R.success(body);
}
}
- 实现
ResponseBodyAdvice重写两个方法 - 添加
@ControllerAdvice注解
测试
新建包controller,并添加TestController测试类
@RestController
@ResponseResult
public class TestController {
@GetMapping("/test")
public Map<String, Object> test() {
HashMap<String, Object> data = new HashMap<>();
data.put("info", "测试成功");
return data;
}
}
- 添加
@ResponseResult注解
启动项目,在默认端口: 8080
浏览器访问地址:localhost:8080/test
{"code":20000,"message":"成功","data":{"info":"测试成功"}}
总结
1、创建code枚举和返回结果实体类
2、自定义一个注解@ResponseResult
3、定义拦截器,拦截请求,判断Controller上是否添加了@ResponseResult注解。如果添加了注解在request中添加注解标记,往下传递
4、添加@ControllerAdvice注解 ,实现ResponseBodyAdvice接口,并重写两个方法,通过判断request中是否有注解标记,如果有就往下执行,进一步包装。没有就直接返回,不需包装。
问题
1、如果要返回错误结果,这种方法显然不方便
@GetMapping("/fail")
public R error() {
int res = 0; // 查询结果数
if(res == 0) {
return R.fail(10001, "没有数据");
}
return R.success(res);
}
2、我们需要对错误和异常进行进一步的封装
封装错误和异常结果
创建错误结果实体
在包pojo中添加ErrorResult实体类
/**
* 异常结果包装类
* @author sw-code
*
*/
public class ErrorResult {
private Integer code;
private String message;
private String exception;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getException() {
return exception;
}
public void setException(String exception) {
this.exception = exception;
}
public static ErrorResult fail(ResultCode resultCode, Throwable e, String message) {
ErrorResult errorResult = ErrorResult.fail(resultCode, e);
errorResult.setMessage(message);
return errorResult;
}
public static ErrorResult fail(ResultCode resultCode, Throwable e) {
ErrorResult errorResult = new ErrorResult();
errorResult.setCode(resultCode.code());
errorResult.setMessage(resultCode.message());
errorResult.setException(e.getClass().getName());
return errorResult;
}
public static ErrorResult fail(Integer code, String message) {
ErrorResult errorResult = new ErrorResult();
errorResult.setCode(code);
errorResult.setMessage(message);
return errorResult;
}
}
自定义异常类
在包pojo中添加BizException实体类,继承RuntimeException
@Data
public class BizException extends RuntimeException {
/**
* 错误码
*/
private Integer code;
/**
* 错误信息
*/
private String message;
public BizException() {
super();
}
public BizException(ResultCode resultCode) {
super(resultCode.message());
this.code = resultCode.code();
this.message = resultCode.message();
}
public BizException(ResultCode resultCode, Throwable cause) {
super(resultCode.message(), cause);
this.code = resultCode.code();
this.message = resultCode.message();
}
public BizException(String message) {
super(message);
this.code = -1;
this.message = message;
}
public BizException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BizException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
this.message = message;
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
全局异常处理类
在包handler中添加GlobalExceptionHandler,添加@RestControllerAdvice注解
/**
* 全局异常处理类
* @RestControllerAdvice(@ControllerAdvice),拦截异常并统一处理
* @author sw-code
*
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理自定义的业务异常
* @param e 异常对象
* @param request request
* @return 错误结果
*/
@ExceptionHandler(BizException.class)
public ErrorResult bizExceptionHandler(BizException e, HttpServletRequest request) {
log.error("发生业务异常!原因是: {}", e.getMessage());
return ErrorResult.fail(e.getCode(), e.getMessage());
}
// 拦截抛出的异常,@ResponseStatus:用来改变响应状态码
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ErrorResult handlerThrowable(Throwable e, HttpServletRequest request) {
log.error("发生未知异常!原因是: ", e);
ErrorResult error = ErrorResult.fail(ResultCode.SYSTEM_ERROR, e);
return error;
}
// 参数校验异常
@ExceptionHandler(BindException.class)
public ErrorResult handleBindExcpetion(BindException e, HttpServletRequest request) {
log.error("发生参数校验异常!原因是:",e);
ErrorResult error = ErrorResult.fail(ResultCode.PARAM_IS_INVALID, e, e.getAllErrors().get(0).getDefaultMessage());
return error;
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.error("发生参数校验异常!原因是:",e);
ErrorResult error = ErrorResult.fail(ResultCode.PARAM_IS_INVALID,e,e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return error;
}
}
- 添加注解
@RestControllerAdvice(@ControllerAdvice),拦截异常并统一处理
修改方法返回值拦截处理器
将错误和异常结果也进行统一封装
/**
* 使用 @ControllerAdvice & ResponseBodyAdvice
* 拦截Controller方法默认返回参数,统一处理返回值/响应体
*/
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
// 标记名称
public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
// 判断是否要执行 beforeBodyWrite 方法,true为执行,false不执行,有注解标记的时候处理返回值
@Override
public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
// 判断请求是否有包装标记
ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
return responseResultAnn == null ? false : true;
}
// 对返回值做包装处理,如果属于异常结果,则需要再包装
@Override
public Object beforeBodyWrite(Object body, MethodParameter arg1, MediaType arg2,
Class<? extends HttpMessageConverter<?>> arg3, ServerHttpRequest arg4, ServerHttpResponse arg5) {
if (body instanceof ErrorResult) {
ErrorResult error = (ErrorResult) body;
return R.fail(error.getCode(), error.getMessage());
} else if (body instanceof R) {
return (R) body;
} else if (body instanceof String) {
return body;
}
return R.success(body);
}
}
测试
@GetMapping("/fail")
public Integer error() {
int res = 0; // 查询结果数
if( res == 0 ) {
throw new BizException("没有数据");
}
return res;
}
返回结果
{"code":-1,"message":"没有数据","data":null}
我们无需担心返回类型,如果需要返回错误提示信息,可以直接抛出自定义异常(BizException),并添加自定义错误信息。
查看源码
SpringBoot统一处理返回结果和异常情况的更多相关文章
- ASP.NET Core搭建多层网站架构【11-WebApi统一处理返回值、异常】
2020/02/01, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[11-WebApi统一处理返回值.异常] 使用I ...
- springboot2.0-统一处理返回结果和异常情况
一.统一处理返回结果和异常处理的原因: 1.在springboot项目里我们希望接口返回的数据包含至少三个属性: a.code:请求接口的返回码,成功或者异常等返回编码,例如定义请求成功,code = ...
- asp.net core webapi 统一处理返回值、异常和请求参数验证
现在的开发模式很少用asp.net mvc一个项目直接操作界面和数据库了.大部分都使用前后端分离,更多的是为了让API支持移动端. 后端写webapi的时候必然需要和前端约定请求值和返回值的格式,如果 ...
- springboot统一ajax返回数据格式,并且jquery ajax success函数修改
服务端代码: package com.zhqn.sc.cfg; import org.springframework.core.MethodParameter; import org.springfr ...
- springboot统一返回json数据格式并配置系统异常拦截
本文链接:https://blog.csdn.net/syystx/article/details/82870217通常进行前后端分离开发时我们需要定义统一的json数据交互格式并对系统未处理异常进行 ...
- SpringBoot系列(十)优雅的处理统一异常处理与统一结果返回
SpringBoot系列(十)统一异常处理与统一结果返回 往期推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列 ...
- 测试开发专题:spring-boot统一异常捕获
java异常介绍 异常时相对于return的一种退出机制,可以由系统触发,也可由程序通过throw语句触发,异常可以通过try/catch语句进行捕获并处理,如果没有捕获,则会导致程序退出并输出异常栈 ...
- springboot实现定时任务,异步操作,统一结果返回,全局异常处理,拦截器及事务处理
本文是作者原创,版权归作者所有.若要转载,请注明出处. 本文都是springboot的常用和实用功能,话不多说开始吧 定时任务 1.启动类开启注解 @EnableScheduling //开启基于注解 ...
- SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!
大家好,我是飘渺. 今天我们来聊一聊在基于SpringBoot前后端分离开发模式下,如何友好的返回统一的标准格式以及如何优雅的处理全局异常. 首先我们来看看为什么要返回统一的标准格式? 为什么要对Sp ...
随机推荐
- CentOS 7系统启动后怎么从命令行模式切换到图形界面模式
CentOS 7系统启动后怎么从命令行模式切换到图形界面模式原创传智播客官方博客 最后发布于2020-04-08 15:44:43 阅读数 88 收藏展开一.存在问题 在VMware虚拟机中成功安装c ...
- LVM 相关知识
LVM 相关知识 一.示例图 二.概念 名词 全称 释义 PV Physical Volume 物理硬盘.硬盘分区或者RAID磁盘阵列,先要创建pv VG Volume Group 卷组建立在物理卷之 ...
- Linux创建RAID1_实战
Linux创建RAID1实战 Linux创建RAID1 RAID1俗称镜像,它最少由两个硬盘组成,且两个硬盘上存储的数据均相同,以实现数据冗余 RAID1读操作速度有所提高,写操作理论上与单硬盘速度一 ...
- CentOS7安装开发工具套件时报错解决方案
操作系统:CentOS 7.2 执行安装命令时显示以下信息: [root@DEV-CMDB-DB02 ~]# yum -y groupinstall "Development Tools&q ...
- unity UGUI填坑 之 HorizontalLayoutGroup 和 ContentSizeFitter配合使用
今天在项目中遇到一个问题,我们的ui过来找我,问为什么Content里的Item显示的不完全 花了半个小时看了一下,发现个小小的坑,记录一下 这些属性是用来实现,Content下的Item的偏移和间隔 ...
- 在 Android 使用 QuickJS JavaScript 引擎教程
quickjs-android 是 QuickJS JavaScript 引擎的 Android 接口框架,整体基于面向对象设计,提供了自动GC功能,使用简单.armeabi-v7a 的大小仅 350 ...
- php代码审计之命令执行中windows/linux的差异化问题
昨天好基友发来了一段代码,还是挺有趣的,记录下: <?php $a = '\''.str_replace("'","\'",$_GET[1]).'\''; ...
- Qt开发笔记:OpenSSL库介绍、windows上mingw32版本的OpenSSL编译模块化
前言 Windows上mingw32版本的openssl的编译是属于比较棘手的,OpenSSL本身不提供支持.. OpenSSL 介绍 OpenSSL是一个开放源代码的软件库包,应用程序可 ...
- GO语言复合类型04---映射
package main import "fmt" /* 映射(map)是键值对(key-value)数据的集合 根据键key可以快速检索值value 键值的类型可以是任意的,ke ...
- Jmeter- 笔记9 - CLI(无图形界面)
使用CLI模式,减少资源占用 用GUI调试好脚本 在jmeter的bin文件夹运行cmd,然后输入命令:jmeter -n -t [jmx file] -l [results file] -e -o ...