SpringBoot统一接口返回及全局异常如何处理
一、SpringBoot不使用统一返回格式
默认情况下,SpringBoot会有如下三种返回情况。
1.1 使用字符串返回
@GetMapping("/getUserName") public String getUserName(){ return "HuaGe"; }
调用接口返回结果:
HuaGe
1.2 使用实体类返回
@GetMapping("/getUserName") public User getUserName(){ return new User("HuaGe",18,"男"); }
调用接口返回结果:
{ "name": "HuaGe", "age": "18", "性别": "男", }
1.3 异常情况下返回
@GetMapping("/getUserName") public static String getUserName(){ HashMap hashMap = Maps.newHashMap(); return hashMap.get(0).toString(); }
模拟一个空指针异常,在不做任何异常处理的情况下,可以看下SpringBoot的默认返回结果:
{ "timestamp": "2021-08-09T06:56:41.524+00:00", "status": 500, "error": "Internal Server Error", "path": "/sysUser/getUserName" }
对于上面这几种情况,如果整个项目没有定义统一的返回格式,五个后台开发人员定义五种返回格式,这样不仅代码臃肿,前后端对接效率低,而且还会有一些意向不到的情况发生,比如前端直接显示异常详情等,这给用户的体验是非常差的。
二、基础玩法
项目中最常见到的是封装一个工具类,类中定义需要返回的字段信息,把需要返回前端的接口信息,通过该类进行封装,这样就可以解决返回格式不统一的现象了。
2.1 参数说明
code: 状态码,后台可以维护一套统一的状态码;
message: 描述信息,接口调用成功/失败的提示信息;
data: 返回数据。
2.2 流程说明
新建Result类
public class Result<T> { private int code; private String message; private T data; public Result() {} public Result(int code, String message) { this.code = code; this.message = message; } /** * 成功 */ public static <T> Result<T> success(T data) { Result<T> result = new Result<T>(); result.setCode(ResultMsgEnum.SUCCESS.getCode()); result.setMessage(ResultMsgEnum.SUCCESS.getMessage()); result.setData(data); return result; } /** * 失败 */ public static <T> Result<T> error(int code, String message) { return new Result(code, message); } }
定义返回状态码
public enum ResultMsgEnum { SUCCESS(0, "成功"), FAIL(-1, "失败"), AUTH_ERROR(502, "授权失败!"), SERVER_BUSY(503, "服务器正忙,请稍后再试!"), DATABASE_OPERATION_FAILED(504, "数据库操作失败"); private int code; private String message; ResultMsgEnum(int code, String message) { this.code = code; this.message = message; } public int getCode() { return this.code; } public String getMessage() { return this.message; } }
使用方式
上面两步定义了数据返回格式
和状态码
,接下来就要看下在接口中如何使用了。
@GetMapping("/getUserName") public Result getUserName(){ return Result.success("huage"); }
调用结果如下,可以看到是我们在Result中定义的参数类型。
{ "code": 0, "message": "成功", "data": "huage" }
这样写虽然能够满足日常需求,而且我相信很多小伙伴也是这么用的,但是如果我们有大量的接口,然后在每一个接口中都使用Result.success
来包装返回信息,会新增很多重复代码,显得不够优雅,甚至都不好意思拿出去显摆。 肯定会有一种方式能够再一次提高代码逼格,实现最优解。
三、进阶用法
基本用法学会后,接下来看点究极版本,主要用到如下两个知识点,用法简单,无论是拿出来教学妹,还是指点小姐姐,都是必备技能。
3.1 类介绍
ResponseBodyAdvice: 该接口是SpringMVC 4.1提供的,它允许在 执行
@ResponseBody
后自定义返回数据,用来封装统一数据格式返回;@RestControllerAdvice: 该注解是对Controller进行增强的,可以全局捕获抛出的异常。
3.2 用法说明
新建
ResponseAdvice
类;实现
ResponseBodyAdvice
接口,实现supports
、beforeBodyWrite
方法;该类用于统一封装controller中接口的返回结果。
@RestControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice<Object> { @Autowired private ObjectMapper objectMapper; /** * 是否开启功能 true:是 */ @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { return true; } /** * 处理返回结果 */ @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { //处理字符串类型数据 if(o instanceof String){ try { return objectMapper.writeValueAsString(Result.success(o)); } catch (JsonProcessingException e) { e.printStackTrace(); } } return Result.success(o); } }
我们可以通过getUserName
接口测试一下,会发现和直接使用Result
返回的结果是一致的。
不过,细心的小伙伴们肯定注意到了,在ResponseAdvice
我们全部使用了Result.success(o)
来处理结果,对于error类型的结果未做处理。我们来看下,发生异常情况时,返回结果是什么样呢?继续使用上面HashMap空指针异常的代码,测试结果如下:
{ "code": 0, "message": "成功", "data": { "timestamp": "2021-08-09T09:33:26.805+00:00", "status": 405, "error": "Method Not Allowed", "path": "/sysUser/getUserName" } }
虽然格式上没有毛病,但是在code、data字段的具体数据上是不友好或不正确的。不处理好这些事情,会严重影响自己在前端妹妹心中的高大形象的,这是决不能容忍的。
3.3 全局异常处理器
以前我们遇到异常时,第一时间想到的应该是try..catch..finnal吧,不过这种方式会导致大量代码重复,维护困难,逻辑臃肿等问题,这不是我们想要的结果。
今天我们要用的全局异常处理方式,用起来是比较简单的。首先新增一个类,增加@RestControllerAdvice
注解,该注解的作用花哥上面已经介绍过,就不再唠叨了。
@RestControllerAdvice public class CustomerExceptionHandler { }
如果我们有想要拦截的异常类型,就新增一个方法,使用@ExceptionHandler
注解修饰,注解参数为目标异常类型。
例如:controller中接口发生Exception异常时,就会进入到Execption
方法中进行捕获,将杂乱的异常信息,转换成指定格式后交给ResponseAdvice
方法进行统一格式封装并返回给前端小伙伴。
@RestControllerAdvice @Slf4j public class CustomerExceptionHandler { @ExceptionHandler(AuthException.class) public String ErrorHandler(AuthorizationException e) { log.error("没有通过权限验证!", e); return "没有通过权限验证!"; } @ExceptionHandler(Exception.class) public Result Execption(Exception e) { log.error("未知异常!", e); return Result.error(ResultMsgEnum.SERVER_BUSY.getCode(),ResultMsgEnum.SERVER_BUSY.getMessage()); } }
再次调用接口getUserName
查看返回结果,会发现还是有一些问题,因为我们在CustomerExceptionHandler
中已经将接口返回结果封装成Result
类型,而代码执行到统一结果返回类ResponseAdvice
时,又会结果再次封装,就出现了如下问题。
{ "code": 0, "message": "成功", "data": { "code": 503, "message": "服务器正忙,请稍后再试!", "data": null } }
3.4 统一返回结果处理类最终版
解决上述问题非常简单,只要在beforeBodyWrite
中增加一条判断即可。
@RestControllerAdvice public class ResponseAdvice implements ResponseBodyAdvice<Object> { @Autowired private ObjectMapper objectMapper; /** * 是否开启功能 true:开启 */ @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { return true; } /** * 处理返回结果 */ @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { //处理字符串类型数据 if(o instanceof String){ try { return objectMapper.writeValueAsString(Result.success(o)); } catch (JsonProcessingException e) { e.printStackTrace(); } } //返回类型是否已经封装 if(o instanceof Result){ return o; } return Result.success(o); } }
以上是SpringBoot统一接口返回及全局异常如何处理的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

Jasypt介绍Jasypt是一个java库,它允许开发员以最少的努力为他/她的项目添加基本的加密功能,并且不需要对加密工作原理有深入的了解用于单向和双向加密的高安全性、基于标准的加密技术。加密密码,文本,数字,二进制文件...适合集成到基于Spring的应用程序中,开放API,用于任何JCE提供程序...添加如下依赖:com.github.ulisesbocchiojasypt-spring-boot-starter2.1.1Jasypt好处保护我们的系统安全,即使代码泄露,也可以保证数据源的

使用场景1、下单成功,30分钟未支付。支付超时,自动取消订单2、订单签收,签收后7天未进行评价。订单超时未评价,系统默认好评3、下单成功,商家5分钟未接单,订单取消4、配送超时,推送短信提醒……对于延时比较长的场景、实时性不高的场景,我们可以采用任务调度的方式定时轮询处理。如:xxl-job今天我们采

一、Redis实现分布式锁原理为什么需要分布式锁在聊分布式锁之前,有必要先解释一下,为什么需要分布式锁。与分布式锁相对就的是单机锁,我们在写多线程程序时,避免同时操作一个共享变量产生数据问题,通常会使用一把锁来互斥以保证共享变量的正确性,其使用范围是在同一个进程中。如果换做是多个进程,需要同时操作一个共享资源,如何互斥呢?现在的业务应用通常是微服务架构,这也意味着一个应用会部署多个进程,多个进程如果需要修改MySQL中的同一行记录,为了避免操作乱序导致脏数据,此时就需要引入分布式锁了。想要实现分

springboot读取文件,打成jar包后访问不到最新开发出现一种情况,springboot打成jar包后读取不到文件,原因是打包之后,文件的虚拟路径是无效的,只能通过流去读取。文件在resources下publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

在Springboot+Mybatis-plus不使用SQL语句进行多表添加操作我所遇到的问题准备工作在测试环境下模拟思维分解一下:创建出一个带有参数的BrandDTO对象模拟对后台传递参数我所遇到的问题我们都知道,在我们使用Mybatis-plus中进行多表操作是极其困难的,如果你不使用Mybatis-plus-join这一类的工具,你只能去配置对应的Mapper.xml文件,配置又臭又长的ResultMap,然后再去写对应的sql语句,这种方法虽然看上去很麻烦,但具有很高的灵活性,可以让我们

SpringBoot和SpringMVC都是Java开发中常用的框架,但它们之间有一些明显的差异。本文将探究这两个框架的特点和用途,并对它们的差异进行比较。首先,我们来了解一下SpringBoot。SpringBoot是由Pivotal团队开发的,它旨在简化基于Spring框架的应用程序的创建和部署。它提供了一种快速、轻量级的方式来构建独立的、可执行

1、自定义RedisTemplate1.1、RedisAPI默认序列化机制基于API的Redis缓存实现是使用RedisTemplate模板进行数据缓存操作的,这里打开RedisTemplate类,查看该类的源码信息publicclassRedisTemplateextendsRedisAccessorimplementsRedisOperations,BeanClassLoaderAware{//声明了key、value的各种序列化方式,初始值为空@NullableprivateRedisSe

在项目中,很多时候需要用到一些配置信息,这些信息在测试环境和生产环境下可能会有不同的配置,后面根据实际业务情况有可能还需要再做修改。我们不能将这些配置在代码中写死,最好是写到配置文件中,比如可以把这些信息写到application.yml文件中。那么,怎么在代码里获取或者使用这个地址呢?有2个方法。方法一:我们可以通过@Value注解的${key}即可获取配置文件(application.yml)中和key对应的value值,这个方法适用于微服务比较少的情形方法二:在实际项目中,遇到业务繁琐,逻
