Spring Boot 2.x(六):优雅的统一返回值
为什么要统一返回值
在我们做后端应用的时候,前后端分离的情况下,我们经常会定义一个数据格式,通常会包含code
,message
,data
这三个必不可少的信息来方便我们的交流,下面我们直接来看代码
ReturnVO
package indi.viyoung.viboot.util;
import java.util.Properties;
/**
* 统一定义返回类
*
* @author yangwei
* @since 2018/12/20
*/
public class ReturnVO {
private static final Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir") + "/viboot-common/src/main/resources/response.properties");
/**
* 返回代码
*/
private String code;
/**
* 返回信息
*/
private String message;
/**
* 返回数据
*/
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
/**
* 默认构造,返回操作正确的返回代码和信息
*/
public ReturnVO() {
this.setCode(properties.getProperty(ReturnCode.SUCCESS.val()));
this.setMessage(properties.getProperty(ReturnCode.SUCCESS.msg()));
}
/**
* 构造一个返回特定代码的ReturnVO对象
* @param code
*/
public ReturnVO(ReturnCode code) {
this.setCode(properties.getProperty(code.val()));
this.setMessage(properties.getProperty(code.msg()));
}
/**
* 默认值返回,默认返回正确的code和message
* @param data
*/
public ReturnVO(Object data) {
this.setCode(properties.getProperty(ReturnCode.SUCCESS.val()));
this.setMessage(properties.getProperty(ReturnCode.SUCCESS.msg()));
this.setData(data);
}
/**
* 构造返回代码,以及自定义的错误信息
* @param code
* @param message
*/
public ReturnVO(ReturnCode code, String message) {
this.setCode(properties.getProperty(code.val()));
this.setMessage(message);
}
/**
* 构造自定义的code,message,以及data
* @param code
* @param message
* @param data
*/
public ReturnVO(ReturnCode code, String message, Object data) {
this.setCode(code.val());
this.setMessage(message);
this.setData(data);
}
@Override
public String toString() {
return "ReturnVO{" +
"code='" + code + '\'' +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
在这里,我提供了几个构造方法以供不同情况下使用。代码的注释已经写得很清楚了,大家也可以应该看的比较清楚~
ReturnCode
细心的同学可能发现了,我单独定义了一个ReturnCode
枚举类用于存储代码和返回的Message:
package indi.viyoung.viboot.util;
/**
* @author yangwei
* @since 2018/12/20
*/
public enum ReturnCode {
/** 操作成功 */
SUCCESS("SUCCESS_CODE", "SUCCESS_MSG"),
/** 操作失败 */
FAIL("FAIL_CODE", "FAIL_MSG"),
/** 空指针异常 */
NullpointerException("NPE_CODE", "NPE_MSG"),
/** 自定义异常之返回值为空 */
NullResponseException("NRE_CODE", "NRE_MSG");
private ReturnCode(String value, String msg){
this.val = value;
this.msg = msg;
}
public String val() {
return val;
}
public String msg() {
return msg;
}
private String val;
private String msg;
}
这里,我并没有将需要存储的数据直接放到枚举中,而是放到了一个配置文件中,这样既可以方便我们进行相关信息的修改,并且阅读起来也是比较方便。
SUCCESS_CODE=2000
SUCCESS_MSG=操作成功
FAIL_CODE=5000
FAIL_MSG=操作失败
NPE_CODE=5001
NPE_MSG=空指针异常
NRE_CODE=5002
NRE_MSG=返回值为空
注意,这里的属性名和属性值分别与枚举类中的value和msg相对应,这样,我们才可以方便的去通过I/O流去读取。
这里需要注意一点,如果你使用的是IDEA编辑器,需要修改以下的配置,这样你编辑配置文件的时候写的是中文,实际上保存的是ASCII字节码。
下面,来看一下读取的工具类:
package indi.viyoung.viboot.util;
import java.io.*;
import java.util.Iterator;
import java.util.Properties;
/**
* 读取*.properties中的属性
* @author vi
* @since 2018/12/24 7:33 PM
*/
public class ReadPropertiesUtil {
public static Properties getProperties(String propertiesPath){
Properties properties = new Properties();
try {
InputStream inputStream = new BufferedInputStream(new FileInputStream(propertiesPath));
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
return properties;
}
}
这里我直接写了一个静态的方法,传入的参数是properties文件的位置,这样的话,本文最初代码中的也就得到了解释。
private static final Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir") + "/viboot-common/src/main/resources/response.properties");
使用ReturnVO
@RequestMapping("/test")
public ReturnVO test(){
try {
//省略
//省略
} catch (Exception e) {
e.printStackTrace();
}
return new ReturnVO();
}
下面我们可以去访问这个接口,看看会得到什么:
但是,现在问题又来了,因为try...catch...
的存在,总是会让代码变得重复度很高,一个接口你都至少要去花三到十秒去写这个接口,如果不知道编辑器的快捷键,更是一种噩梦。我们只想全心全意的去关注实现业务,而不是花费大量的时间在编写一些重复的"刚需"代码上。
使用AOP进行全局异常的处理
(这里,我只是对全局异常处理进行一个简单的讲解,后面也就是下一节中会详细的讲述)
/**
* 统一封装返回值和异常处理
*
* @author vi
* @since 2018/12/20 6:09 AM
*/
@Slf4j
@Aspect
@Order(5)
@Component
public class ResponseAop {
private static final Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir") + "/viboot-common/src/main/resources/response.properties");
/**
* 切点
*/
@Pointcut("execution(public * indi.viyoung.viboot.*.controller..*(..))")
public void httpResponse() {
}
/**
* 环切
*/
@Around("httpResponse()")
public ReturnVO handlerController(ProceedingJoinPoint proceedingJoinPoint) {
ReturnVO returnVO = new ReturnVO();
try {
//获取方法的执行结果
Object proceed = proceedingJoinPoint.proceed();
//如果方法的执行结果是ReturnVO,则将该对象直接返回
if (proceed instanceof ReturnVO) {
returnVO = (ReturnVO) proceed;
} else {
//否则,就要封装到ReturnVO的data中
returnVO.setData(proceed);
}
} catch (Throwable throwable) {
//如果出现了异常,调用异常处理方法将错误信息封装到ReturnVO中并返回
returnVO = handlerException(throwable);
}
return returnVO;
}
/**
* 异常处理
*/
private ReturnVO handlerException(Throwable throwable) {
ReturnVO returnVO = new ReturnVO();
//这里需要注意,返回枚举类中的枚举在写的时候应该和异常的名称相对应,以便动态的获取异常代码和异常信息
//获取异常名称的方法
String errorName = throwable.toString();
errorName = errorName.substring(errorName.lastIndexOf(".") + 1);
//直接获取properties文件中的内容
returnVO.setMessage(properties.getProperty(ReturnCode.valueOf(errorName).msg()));
returnVO.setCode(properties.getProperty(ReturnCode.valueOf(errorName).val()));
return returnVO;
}
}
如果,我们需要在每一个项目中都可以这么去做,需要将这个类放到一个公用的模块中,然后在pom中导入这个模块
<dependency>
<groupId>indi.viyoung.course</groupId>
<artifactId>viboot-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
这里需要注意一点,必须保证你的切点的正确书写!!否则就会导致切点无效,同时需要在启动类中配置:
@ComponentScan(value = "indi.viyoung.viboot.*")
导入的正是common
包下的所有文件,以保证可以将ResponseAop
这个类加载到Spring的容器中。
下面我们来测试一下,访问我们经过修改后的编写的findAll
接口:
@RequestMapping("/findAll")
public Object findAll(){
return userService.list();
}
PS:这里我将返回值统一为Object,以便数据存入data
,实际类型应是Service
接口的返回类型。如果没有返回值的话,那就可以new
一个ReturnVO
对象直接通过构造方法赋值即可。关于返回类型为ReturnVO
的判断,代码中也已经做了特殊的处理,并非存入data
,而是直接返回。
下面,我们修改一下test方法,让他抛出一个我们自定义的查询返回值为空的异常:
@RequestMapping("/test")
public ReturnVO test(){
throw new NullResponseException();
}
下面,我们再来访问以下test接口:
可以看到,正如我们properties中定义的那样,我们得到了我们想要的消息。
源码可以去github或者码云上进行下载,后续的例子都会同步更新。
公众号
原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。
Spring Boot 2.x(六):优雅的统一返回值的更多相关文章
- Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志
其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...
- spring boot / cloud (十六) 分布式ID生成服务
spring boot / cloud (十六) 分布式ID生成服务 在几乎所有的分布式系统或者采用了分库/分表设计的系统中,几乎都会需要生成数据的唯一标识ID的需求, 常规做法,是使用数据库中的自动 ...
- Spring Boot 2.X(六):Spring Boot 集成Redis
Redis 简介 什么是 Redis Redis 是目前使用的非常广泛的免费开源内存数据库,是一个高性能的 key-value 数据库. Redis 与其他 key-value 缓存(如 Memcac ...
- Spring Boot 学习笔记(六) 整合 RESTful 参数传递
Spring Boot 学习笔记 源码地址 Spring Boot 学习笔记(一) hello world Spring Boot 学习笔记(二) 整合 log4j2 Spring Boot 学习笔记 ...
- Spring boot Jpa添加对象字段使用数据库默认值
Spring boot Jpa添加对象字段使用数据库默认值 jpa做持久层框架,项目中数据库字段有默认值和非空约束,这样在保存对象是必须保存一个完整的对象,但在开发中我们往往只是先保存部分特殊的字段其 ...
- Spring Boot 2.0 教程 | AOP 切面统一打印请求日志
欢迎关注微信公众号: 小哈学Java 文章首发于个人网站 https://www.exception.site/springboot/spring-boot-aop-web-request 本节中,您 ...
- Spring Boot中Restful Api的异常统一处理
我们在用Spring Boot去向前端提供Restful Api接口时,经常会遇到接口处理异常的情况,产生异常的可能原因是参数错误,空指针异常,SQL执行错误等等. 当发生这些异常时,Spring B ...
- Spring Boot 2.0(六):使用 Docker 部署 Spring Boot 开源软件云收藏
云收藏项目已经开源2年多了,作为当初刚开始学习 Spring Boot 的练手项目,使用了很多当时很新的技术,现在看来其实很多新技术是没有必要使用的,但做为学习案例来讲确实是一个绝佳的 Spring ...
- Spring Boot 入门(六):集成 treetable 和 zTree 实现树形图
本篇文章是接着Spring Boot 入门(五):集成 AOP 进行日志管理写的,主要集成了树形图,在部门列表或者权限列表中,树形图经常被用上.主要是根据相应的 API 凭借 html 字符串 1.t ...
随机推荐
- Google FireBase - fcm 推送 (Cloud Messaging)
要将 Firebase 添加到您的应用,您需要有一个 Firebase 项目以及适用于您的应用的 Firebase 配置文件. 如果您还没有 Firebase 项目,请在 Firebase 控制台中创 ...
- 页面的input唤醒软键盘再收起后,页面会出现软键盘高度的空白背景
微信浏览器在版本6.7.4及以上会有这个bug:页面的input唤醒软键盘再收起后,页面会出现软键盘高度的空白背景,触摸到滚动条会消失恢复! 解决代码 后台框架嵌入iframe的情景,iframe内部 ...
- SpringCloud 在Feign上使用Hystrix(断路由)
SpringCloud 在Feign上使用Hystrix(断路由) 第一步:由于Feign的起步依赖中已经引入了Hystrix的依赖,所以只需要开启Hystrix的功能,在properties文件中 ...
- jquery项目中一些常用方法
1.获取url中的参数 function getUrlParam(name) { var reg = new RegExp("(^|&)" + name + &quo ...
- flume安装
1.flume安装 将压缩包减压到当前目录 tar -zxf apache-flume-1.8.0-bin.tar.gz 配置环境变量 编辑当前目录中的 .bashrc 文件(这影响当前用户的环 ...
- Python基础之面向对象2(封装)
一.封装定义: 二.作用 三.私有成员: 1.基本概念及作用 2.__slots__手段私有成员: 3.@property属性手段私有成员: 四.基础示例代码 1.用方法封装变量 "&quo ...
- django 利用pillow 进行简单的设置验证码(python)
1.导入模块 并定义一个验证状态 from PIL import Image, ImageDraw, ImageFont from django.utils.six import BytesIO de ...
- Django 简单的使用
1.创建一个名字为 two 的项目 并 进入项目 2.创建一个 app 3.更改语言和时间 4,注册APP 5.模板创建和设置 设置模板查找的路径 6,然后我们开始设置 路由映射 主项目映射 然后我们 ...
- 小程序textarea完美填坑
相信做微信小程序的码友们都被textarea这个原生组件坑过,什么placeholder位置错乱,穿透弹窗或遮罩层,ios上输入法弹起后换行输入内容遮挡,删除输入内容时内容被遮挡等等... 反正综上所 ...
- Oracle--配置并保存PL/SQL Developer界面
之前一直用SQL Server,现在刚接触Oracle,用PL/SQL Developer 客户端,在设置自已的使用习惯后保存界面 PL/SQL Developer初始界面布局,当你设置后,重新启动, ...