springboot项目统一封装返回值和异常处理(方式一)

为什么要统一返回值:

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

import java.util.Properties;
import lombok.Data;

/**
 * 统一定义返回类
 *
 */
@Data
public class ReturnVO {

    //获取程序当前路径下的文件
    private static final Properties properties = ReadPropertiesUtil
        .getProperties(System.getProperty("user.dir") + "/src/main/resources/response.properties");

    /**
     * 返回代码
     */
    private String code;

    /**
     * 返回信息
     */
    private String message;

    /**
     * 返回数据
     */
    private Object data;

/** * 默认构造,返回操作正确的返回代码和信息 */ 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 com.house.common;

public enum ReturnCode {

    /** 操作成功 */
    SUCCESS("SUCCESS_CODE", "SUCCESS_MSG"),

    /** 操作失败 */
    FAIL("FAIL_CODE", "FAIL_MSG"),

    /** 空指针异常 */
    NullpointerException("NPE_CODE", "NPE_MSG"),

    /** 自定义异常之返回值为空 */
    NullPointerException("NRE_CODE", "NRE_MSG");


    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;
}

这里,我并没有将需要存储的数据直接放到枚举中,而是放到了一个配置文件中response.properties,这样既可以方便我们进行相关信息的修改,并且阅读起来也是比较方便。

注意,这里的属性名和属性值分别与枚举类中的value和msg相对应,这样,我们才可以方便的去通过I/O流去读取。

SUCCESS_CODE = 1000
SUCCESS_MSG = success
NRE_CODE = 1021
NRE_MSG = exception001

(如不定义中文字段则跳过这步骤)  这里需要注意一点,如果你使用的是IDEA编辑器,需要修改以下的配置,这样你编辑配置文件的时候写的是中文,实际上保存的是ASCII字节码。

 下面,来看一下读取的工具类:

package com.house.common;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

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;
    }
}

测试使用ReturnVO返回定义的格式:

@RequestMapping("/test")
public ReturnVO test(){
   try {
        //省略
        //省略
    }  catch (Exception e) {
            e.printStackTrace();
   }
     return new ReturnVO();
}

下面我们可以去访问这个接口,看看会得到什么:

 

 但是,现在问题又来了,因为try...catch...的存在,总是会让代码变得重复度很高,一个接口你都至少要去花三到十秒去写这个接口,如果不知道编辑器的快捷键,更是一种噩梦。

我们只想全心全意的去关注实现业务,而不是花费大量的时间在编写一些重复的"刚需"代码上。

使用AOP进行全局异常的处理

(这里,我只是对全局异常处理进行一个简单的讲解,后面也就是下一节中会详细的讲述):

package com.house.common;

import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 统一封装返回值和异常处理
 *
 */
@Slf4j
@Aspect
@Order(5)
@Component
public class ResponseAop {

    //获取程序当前路径下的文件
    private static final Properties properties = ReadPropertiesUtil.getProperties(System.getProperty("user.dir")
        + "/src/main/resources/response.properties");

     // 切点(在包下的所有controller以及他的子包)
    //@Pointcut("execution(public * com.house.controller.*.controller..*(..))")
    @Pointcut("execution(public * com.house.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;
    }
}
      <!--在使用切面注解@Aspect-->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <scope>test</scope>
      </dependency>

      <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
      </dependency>

下面我们来测试一下,访问我们经过修改后的编写的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();
    }

下一篇,用springboot自带的注解@ControllerAdvice 实现统一异常处理。

 

posted @ 2020-11-17 16:07  威兰达  阅读(4611)  评论(0编辑  收藏  举报