为什么要统一返回值
在我们做后端应用的时候, 前后端分离的情况下, 我们经常会定义一个数据格式, 通常会包含 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 中定义的那样, 我们得到了我们想要的消息.
原创文章, 文笔有限, 才疏学浅, 文中若有不正之处, 万望告知.
源码可以去 https://github.com/viyog/viboot/ 或者码云 https://gitee.com/yw18530069930/viboot 上进行下载, 后续的例子都会同步更新.
云撸猫
公众号
--- 恢复内容结束 ---
为什么要统一返回值
在我们做后端应用的时候, 前后端分离的情况下, 我们经常会定义一个数据格式, 通常会包含 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 中定义的那样, 我们得到了我们想要的消息.
原创文章, 文笔有限, 才疏学浅, 文中若有不正之处, 万望告知.
源码可以去 https://github.com/viyog/viboot/ 或者码云 https://gitee.com/yw18530069930/viboot 上进行下载, 后续的例子都会同步更新.
来源: https://www.cnblogs.com/viyoung/p/10188456.html