简介

当使用SpringBoot开发Web项目的API时,为了与前端更好地通信,通常会约定好接口的响应格式。例如,以下是一个JSON格式的响应,通过返回码和返回信息告知前端具体的操作结果或错误信息。如果操作成功,前端可以通过"data"字段获取响应内容。

{
"code":"000000",
"message":"操作成功",
"data": true
}

如果所有接口都这样一个一个的封,那开发人员估计就先一个一个的疯了,为了减少手动的处理过程,让开发人员可以专注于业务本身,本文将向你展示一种非常优雅的方式。

除了统一处理接口的响应内容以外,在一般的业务流程中,不管是自定义的异常还是因为各种问题而抛出的异常,在前端接收到的接口响应状态都是500错误,接口响应的内容上也非常不友好,如下图。



而实际的业务中,我们更希望可以给用户一个相对友好的提示,之前展示了一个成功的响应,这里我们也可以将异常情况处理为同样的结构,例如:

{
"code":"010101",
"message":"订单当前不支持此操作",
"data": ""
}

这样也就可以让前端通过特定的方式将异常信息以更友好的方式展示给用户,而不是干巴巴的代码,接下来我们废话不多说,开干!!!

前期准备

首先我们需要准备一个示例工程用于实操演示,这里给大家准备了一个项目并开源到了Gitee,大家可以执行以下命令获取当前需要使用的示例项目,就是最基础的SpringBoot项目,大家也可以自己初始化一个,这个项目后续我会继续往里添加功能,大家可以关注下,说不定什么时候就可以用上了,也欢迎大家通过各种方式与我沟通相关问题。

git clone https://gitee.com/itartisans/itartisans-framework.git
git checkout base-springboot-web

统一封装报文

为了统一处理业务代码返回的逻辑,我们需要准备一个实体类,核心代码就是以下属性,分别对应着开头的报文各个属性,而其他的getter和setter方法则因为篇幅原因省略了。

public class Result {
private String code;
private String message;
private Object data;
// todo 添加的省略getter和setter方法
}

报文的统一处理需要依赖Spring提供的AOP支持,Spring官方针对响应内容的处理提供了ResponseBodyAdvice接口,对响应报文的封装则需要实现此接口。

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> { @Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
} @Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
Result result = new Result();
result.setCode("000000");
result.setMessage("操作成功");
result.setData(body);
return result;
}
}

说明:

  • 第一行的@ControllerAdvice声明此类是一个切片处理类;
  • supports方法用于标识切面的覆盖范围,如果返回true则会执行beforeBodyWrite方法里的代码,如果返回false则直接响应;
  • beforeBodyWrite方法用于对响应值进行自定义操作,此处通过Result进行封装;

    完成以上处理后,我们可以创建个Controller请求下看看返回结果,如果示例代码是返回的不是String类型的话,那么应该是可以正常响应的,但是如果你尝试的是String,你就会收到这么一个异常:
class org.itartisans.framework.model.entity.Result cannot be cast to class java.lang.String

收到这个异常的原因是Spring默认使用的序列化器是StringHttpMessageConverter,这个序列化器无法正常的将对象转换为String类型,需要手动指定使用MappingJackson2HttpMessageConverter进行处理,而指定序列化处理器则需要实现WebMvcConfigurer并通过重写configureMessageConverters方法实现,具体代码如下:

@Configuration
public class WebConfiguration implements WebMvcConfigurer { @Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new MappingJackson2HttpMessageConverter());
}
}

完成序列化处理器的配置后即可愉快的测试所有类型的返回值了。

统一异常处理

在完成以上的报文封装后,如果代码执行过程中出现了异常,则会出现以下内容,此处是手动throw异常模拟的情况。

{
"code": "000000",
"message": "操作成功",
"data": {
"timestamp": 1691503634161,
"status": 500,
"error": "Internal Server Error",
"path": "/demo/getObject"
}
}

报文即使在失败时也显示了操作成功,这明显不是正常的情况,也不符合我们最初可以定制错误码和错误信息的需求,统一处理异常同样需要使用AOP,创建一个针对异常的切面处理器,代码如下:

@RestControllerAdvice
public class ExceptionAdvice { @ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
Result result = new Result();
result.setCode("999999");
result.setMessage("操作失败");
return result;
} }

这部分代码和封装报文时有共同指出,这里介绍下不一样的地方:

  • 第一行的@RestControllerAdvice与@RestController类似,表示此类中的方法返回值均为JSON类型
  • 第四行的@ExceptionHandler指定该方法时一个异常处理器,参数对应处理的异常,这里指定的Exception.class表示处理所有异常

经过切面处理后报文如下,可以返现data中的返回内容正是我们想要的响应报文。

{
"code": "000000",
"message": "操作成功",
"data": {
"code": "999999",
"message": "操作失败",
"data": null
}
}

这里则是因为上一步统一封装报文时我们没有对方法的返回值进行区分,一刀切的对所有返回值进行了封装,结果出现了这种情况。既然知道了问题所在,那就好解决了,在封装报文时我们知道ResponseAdvice中的supports方法是用于判断是否执行beforeBodyWrite的,那我们只需要在supports中判断返回值类型是否为Result即可,如果不是才进行封装,如果是则直接返回。代码如下:

@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return !returnType.getParameterType().equals(Result.class);
}

修改后重启应用再次请求接口即可发现报文格式符合我们预期了。

{
"code": "999999",
"message": "操作失败",
"data": null
}

自定义异常信息

到此为止我们完成了异常的统一处理,但是目前所有的异常都会返回999999,无法进行自定义错误内容,显然还没有填完开头挖的坑,所以我们继续对程序进行改造,在介绍@ExceptionHandler我们提到参数里可以指定处理的异常类型,那我们就从此处入手,自定义一个异常,然后增加一个对应的异常处理器,在异常处理器中根据异常中的错误信息进行转换。

因为要自定义异常,不可能将异常信息散落在代码里,所以要选择一个地方统一维护异常代码和对应的异常信息,这里作者选择的是枚举类,如果项目有其他要求也可以选择properties文件等载体,只是读取信息时方式不一样而已。枚举类代码如下,添加此枚举之后大家也可以把之前涉及SUCCESS和FAILED的地方重构一下,这里就不展开了:

public enum ResultCode {

    SUCCESS("000000", "操作成功"),
FAILED("999999", "操作失败"),
DEMO_ERROR("010101", "订单当前不支持此操作")
; private final String code;
private final String message; ResultCode(String code, String message) {
this.code = code;
this.message = message;
}
// TODO 添加getter和setter方法
}

有了异常信息列表后我们需要创建一个自定义异常作为信息载体,以便可以在代码中使用throw触发异常处理器,代码如下:

public class FrameworkException extends Exception{

    private ResultCode resultCode;

    public FrameworkException(ResultCode resultCode) {
super(resultCode.getMessage() + "(" + resultCode.getCode() + ")");
this.resultCode = resultCode;
}
// TODO 添加getter和setter方法
}

之后我们要添加对应的异常处理器,在ExceptionAdvice中增加如下方法,用于处理我们刚刚添加的自定义异常。

@ExceptionHandler(FrameworkException.class)
public Result handleFrameworkException(FrameworkException e) {
ResultCode code = e.getResultCode();
Result result = new Result();
result.setCode(code.getCode());
result.setMessage(code.getMessage());
return result;
}

之后我们在测试方法中任意位置添加如下测试代码,注意添加之后之前的代码会报错,因为异常后的代码无法执行到会导致编译报错,这里注释掉即可。

throw new FrameworkException(ResultCode.DEMO_ERROR);

更新后重启项目再次请求接口即可看到已经完成了异常信息的自定义,前端也可以针对性的进行业务处理了。完结!撒花~

{
"code": "010101",
"message": "订单当前不支持此操作",
"data": null
}

SpringBoot项目统一处理返回值和异常的更多相关文章

  1. ASP.NET Core搭建多层网站架构【11-WebApi统一处理返回值、异常】

    2020/02/01, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[11-WebApi统一处理返回值.异常] 使用I ...

  2. asp.net core webapi 统一处理返回值、异常和请求参数验证

    现在的开发模式很少用asp.net mvc一个项目直接操作界面和数据库了.大部分都使用前后端分离,更多的是为了让API支持移动端. 后端写webapi的时候必然需要和前端约定请求值和返回值的格式,如果 ...

  3. SpringBoot统一处理返回结果和异常情况

    如果文章有帮助到你,还请点个赞或留下评论 原因 在springboot项目里我们希望接口返回的数据包含至少三个属性: code:请求接口的返回码,成功或者异常等返回编码,例如定义请求成功. messa ...

  4. Springboot项目统一异常处理

    Springboot项目统一异常处理 一.接口返回值封装 1. 定义Result对象,作为通用返回结果封装 2. 定义CodeMsg对象,作为通用状态码和消息封装 二.定义全局异常类 三.定义异常处理 ...

  5. SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!

    大家好,我是飘渺. 今天我们来聊一聊在基于SpringBoot前后端分离开发模式下,如何友好的返回统一的标准格式以及如何优雅的处理全局异常. 首先我们来看看为什么要返回统一的标准格式? 为什么要对Sp ...

  6. .NetCore Web Api 利用ActionFilterAttribute统一接口返回值格式

    .Net Core 同 Asp.Net MVC一样有几种过滤器,这里不再赘述每个过滤器的执行顺序与作用. 在实际项目开发过程中,统一API返回值格式对前端或第三方调用将是非常必要的,在.NetCore ...

  7. Java开发学习(十八)----AOP通知获取数据(参数、返回值、异常)

    前面的博客我们写AOP仅仅是在原始方法前后追加一些操作,接下来我们要说说AOP中数据相关的内容,我们将从获取参数.获取返回值和获取异常三个方面来研究切入点的相关信息. 前面我们介绍通知类型的时候总共讲 ...

  8. 使用 ResponseBodyAdvice 拦截Controller方法默认返回参数,统一处理返回值/响应体

    使用 @ControllerAdvice & ResponseBodyAdvice 拦截Controller方法默认返回参数,统一处理返回值/响应体 1.Controller代码 以下是Con ...

  9. SpringBoot项目中处理返回json的null值

    在后端数据接口项目开发中,经常遇到返回的数据中有null值,导致前端需要进行判断处理,否则容易出现undefined的情况,如何便捷的将null值转换为空字符串? 以SpringBoot项目为例,SS ...

  10. SpringBoot 如何统一后端返回格式

    在前后端分离的项目中后端返回的格式一定要友好,不然会对前端的开发人员带来很多的工作量.那么SpringBoot如何做到统一的后端返回格式呢?今天我们一起来看看. 为什么要对SpringBoot返回统一 ...

随机推荐

  1. 2020-09-10:java里Object类有哪些方法?

    福哥答案2020-09-10: registerNatives:private+static.getClass:返回此 Object 的运行时类. hashCode:返回该对象的哈希码值.equals ...

  2. Django4全栈进阶之路15 项目实战(用户管理):login.html登录画面设计

    要编写登录页面,我们需要完成以下步骤: template文件夹中创建 login.html 模板文件,输入以下 HTML 代码: {% extends 'base.html' %} {% block ...

  3. SqliLabs 第六关 报错注入!!!

    点开网址,首先看到一个页面,首先尝试闭合字符 id=1 报错 id=1' 报错 id=1"成功 然后开始爆字段?id=1" order by 3 --+ 发现有三个字段然后查询显示 ...

  4. ADG级联备库环境PSU应用验证

    上篇文章 源端为备库的场景下Duplicate失败问题 我只在中间备库环境应用了PSU,解决了级联备库从中间备库duplicate数据库的问题: 细心的朋友已经发现,因为是备库环境,并没有做数据库执行 ...

  5. 快速上手kettle(一)壶之简介

    一.前言 最近由于工作需要,需要用到kettle工具进行数据迁移转换.特意找资料学习了一下,kettle基本操作算是学会了. 所学的也结合实际工作进行了验证.为了防止以后用到忘记了,便写了几篇文章记录 ...

  6. 让你的代码动起来:Python进度条神器tqdm详解及应用实例

    各位Python高手,今天我要给大家介绍一个好用的库,它就是:tqdm tqdm在阿拉伯语中的意思是 "进展",所以这个库也被称为 "快速进展条".不得不说,这 ...

  7. htop 和 bashtop 的一些不足

    htop 和 bashtop 都是 Linux 资源监视器中非常好用的工具,尤其对于展示当前 Linux 操作系统的处理器.内存.硬盘.网络和进程等各项资源的使用情况与状态.但它们都有一个问题,就是当 ...

  8. JMH – Java基准测试

    官方资源 官方Github样例 应用场景 对要使用的数据结构不确定,不知道谁的性能更好 对历史方法代码重构,要评判改造之后的性能提升多少 (我要做的场景) 想准确地知道某个方法需要执行多长时间,以及执 ...

  9. 阿里云 MongoDB 创建库添加用户并授权

    先通过 root 进到 admin 库, 右击test 选择用户管理 测试连接

  10. Vue基础技术之——数据绑定

    Vue数据大致绑定分为两类: 1.单向数据绑定(v-bind): 数据只能从data流向页面. 2.双向数据绑定(v-model):数据不仅能从data流向页面,还能从页面流向data. 话不多说,先 ...