为什么要统一返回值

在我们做后端应用的时候,前后端分离的情况下,我们经常会定义一个数据格式,通常会包含codemessagedata这三个必不可少的信息来方便我们的交流,下面我们直接来看代码

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(六):优雅的统一返回值的更多相关文章

  1. Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

    其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...

  2. spring boot / cloud (十六) 分布式ID生成服务

    spring boot / cloud (十六) 分布式ID生成服务 在几乎所有的分布式系统或者采用了分库/分表设计的系统中,几乎都会需要生成数据的唯一标识ID的需求, 常规做法,是使用数据库中的自动 ...

  3. Spring Boot 2.X(六):Spring Boot 集成Redis

    Redis 简介 什么是 Redis Redis 是目前使用的非常广泛的免费开源内存数据库,是一个高性能的 key-value 数据库. Redis 与其他 key-value 缓存(如 Memcac ...

  4. Spring Boot 学习笔记(六) 整合 RESTful 参数传递

    Spring Boot 学习笔记 源码地址 Spring Boot 学习笔记(一) hello world Spring Boot 学习笔记(二) 整合 log4j2 Spring Boot 学习笔记 ...

  5. Spring boot Jpa添加对象字段使用数据库默认值

    Spring boot Jpa添加对象字段使用数据库默认值 jpa做持久层框架,项目中数据库字段有默认值和非空约束,这样在保存对象是必须保存一个完整的对象,但在开发中我们往往只是先保存部分特殊的字段其 ...

  6. Spring Boot 2.0 教程 | AOP 切面统一打印请求日志

    欢迎关注微信公众号: 小哈学Java 文章首发于个人网站 https://www.exception.site/springboot/spring-boot-aop-web-request 本节中,您 ...

  7. Spring Boot中Restful Api的异常统一处理

    我们在用Spring Boot去向前端提供Restful Api接口时,经常会遇到接口处理异常的情况,产生异常的可能原因是参数错误,空指针异常,SQL执行错误等等. 当发生这些异常时,Spring B ...

  8. Spring Boot 2.0(六):使用 Docker 部署 Spring Boot 开源软件云收藏

    云收藏项目已经开源2年多了,作为当初刚开始学习 Spring Boot 的练手项目,使用了很多当时很新的技术,现在看来其实很多新技术是没有必要使用的,但做为学习案例来讲确实是一个绝佳的 Spring ...

  9. Spring Boot 入门(六):集成 treetable 和 zTree 实现树形图

    本篇文章是接着Spring Boot 入门(五):集成 AOP 进行日志管理写的,主要集成了树形图,在部门列表或者权限列表中,树形图经常被用上.主要是根据相应的 API 凭借 html 字符串 1.t ...

随机推荐

  1. mysql import error

    mysql导入文件一直出错,显示ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option s ...

  2. mysql_Navicat数据库破解

    Navicat Premium 12.1.16.0安装与激活 Navicat Premium 12是一套数据库开发管理工具,支持连接 MySQL.Oracle等多种数据库,可以快速轻松地创建.管理和维 ...

  3. vue的风格指南(强烈推荐)

    1.组件文件 只要有能够拼接文件的构建系统,就把每个组件单独分成文件. 当你需要编辑一个组件或查阅一个组件的用法时,可以更快速的找到它. 2.单文件组件文件的大小写 单文件组件的文件名应该要么始终是单 ...

  4. go语言基础之range

    在go语言中,遍历数据或切片时可以用range,range会产生两个值,分别是数据的索引与值: package main import "fmt" func main() { x ...

  5. Qt5和VS2017建立开发环境,安装后新建项目找不到Qt选项!!!

    最近开发win驱动和Qt5测试程序,需要建立Qt5和VS2017开发环境---对于Qt5和VS2017安装这里不做多余叙述. 参考资源很多,讲解也不错!! 这里切入正题:在VS2017中安转Qt vs ...

  6. Session Cookie介绍和使用

    Cookie机制 Cookie机制 Cookie是服务器存储在本地计算机上的小块文本,并随每个请求发送到同一服务器. IETF RFC 2965 HTTP状态管理机制是一种通用的cookie规范. W ...

  7. LoadRunner(三)——LR相关概念&组成部分

    参考学习感谢:<精通软件性能测试与LoadRunner实战> 一.运行机制和主要组成部分 1.LoadRunner主要由VuGen.Controller和Analysis三部分构成: 2. ...

  8. requirejs + sass 实现的前端及 grunt 自动化构建

    对于 现在的 vue . react .webpack 来说也许有点旧了,有时候,越简单的技术越可靠,备份一下 module.exports = function(grunt) { // Projec ...

  9. Oracle分析函数——函数列表

    --------------聚合函数 SUM :该函数计算组中表达式的累积和 MIN :在一个组中的数据窗口中查找表达式的最小值 MAX :在一个组中的数据窗口中查找表达式的最大值 AVG :用于计算 ...

  10. FCC(ES6写法)Pairwise

    举个例子:有一个能力数组[7,9,11,13,15],按照最佳组合值为20来计算,只有7+13和9+11两种组合.而7在数组的索引为0,13在数组的索引为3,9在数组的索引为1,11在数组的索引为2. ...