在我们做后端应用的时候,前后端分离的情况下,我们经常会定义一个数据格式,通常会包含 code
, message
, data
这三个必不可少的信息来方便我们的交流,下面我们直接来看代码
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
枚举类用于存储代码和返回的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");
@RequestMapping("/test") public ReturnVO test(){ try { //省略 //省略 } catch (Exception e) { e.printStackTrace(); } return new ReturnVO(); }
下面我们可以去访问这个接口,看看会得到什么:
但是,现在问题又来了,因为try...catch...
的存在,总是会让代码变得重复度很高,一个接口你都至少要去花三到十秒去写这个接口,如果不知道编辑器的快捷键,更是一种噩梦。我们只想全心全意的去关注实现业务,而不是花费大量的时间在编写一些重复的"刚需"代码上。
(这里,我只是对全局异常处理进行一个简单的讲解,后面也就是下一节中会详细的讲述)
/** * 统一封装返回值和异常处理 * * @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上进行下载,后续的例子都会同步更新。