资讯专栏INFORMATION COLUMN

Spring Boot 2.x(六):优雅的统一返回值

shaonbean / 2063人阅读

摘要:下面我们来测试一下,访问我们经过修改后的编写的接口这里我将返回值统一为,以便数据存入,实际类型应是接口的返回类型。如果没有返回值的话,那就可以一个对象直接通过构造方法赋值即可。

为什么要统一返回值

在我们做后端应用的时候,前后端分离的情况下,我们经常会定义一个数据格式,通常会包含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中导入这个模块

        
            indi.viyoung.course
            viboot-common
            1.0-SNAPSHOT
        

这里需要注意一点,必须保证你的切点的正确书写!!否则就会导致切点无效,同时需要在启动类中配置:

@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或者码云上进行下载,后续的例子都会同步更新。

公众号

原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75664.html

相关文章

  • Spring Boot 2.x(七):全局处理异常

    摘要:全局异常处理类用于全局返回,如需返回请使用继承了,对于一些类似于请求方式异常的异常进行捕获重写,自定义处理过程这里将异常直接传给方法进行处理,返回值为保证友好的返回,而不是出现错误码。 前言 异常的处理在我们的日常开发中是一个绕不过去的坎,在Spring Boot 项目中如何优雅的去处理异常,正是我们这一节课需要研究的方向。 异常的分类 在一个Spring Boot项目中,我们可以把异...

    ivyzhang 评论0 收藏0
  • Spring Boot 2.x(十):构建优雅RESTful接口

    摘要:满足这些约束条件和原则的应用程序或设计就是。需要注意的是,是设计风格而不是标准。同一个路径,因为请求方式的不同,而去找寻不同的接口,完成对资源状态的转变。一个符合风格的就可以称之一个的接口。 RESTful 相信在座的各位对于RESTful都是略有耳闻,那么RESTful到底是什么呢? REST(Representational State Transfer)表述性状态转移是一组架构约...

    nevermind 评论0 收藏0
  • Spring Boot 2.x(十一):AOP实战--打印接口日志

    摘要:接口日志有啥用在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息。在切入点返回内容之后切入内容可以用来对处理返回值做一些加工处理。 接口日志有啥用 在我们日常的开发过程中,我们可以通过接口日志去查看这个接口的一些详细信息。比如客户端的IP,客户端的类型,响应的时间,请求的类型,请求的接口方法等等,我们可以对这些数据进行统计分析,提取出我们想要的信息。 怎么拿到接口...

    Youngdze 评论0 收藏0
  • Spring框架之我见()——Spring Cloud

    摘要:系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。传统架构升级困难。新的轻量级协议容器化的出现。熔断处理在微服务出现问题时防止出现雪崩效应。 聊完Spring Boot,我们来看看Spring Boot最重要的一方面的应用——Spring Cloud。 Spring Cloud 再聊SpringCloud之前我们先聊聊微服务。 ...

    alighters 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<