目录

* 为什么要统一返回值 <https://www.cnblogs.com/viyoung/p/10188456.html#为什么要统一返回值>
* ReturnVO <https://www.cnblogs.com/viyoung/p/10188456.html#returnvo>
* ReturnCode <https://www.cnblogs.com/viyoung/p/10188456.html#returncode>
* 使用ReturnVO <https://www.cnblogs.com/viyoung/p/10188456.html#使用returnvo>
* 使用AOP进行全局异常的处理
<https://www.cnblogs.com/viyoung/p/10188456.html#使用aop进行全局异常的处理>
* 云撸猫 <https://www.cnblogs.com/viyoung/p/10188456.html#云撸猫>
* 公众号 <https://www.cnblogs.com/viyoung/p/10188456.html#公众号>
为什么要统一返回值

在我们做后端应用的时候,前后端分离的情况下,我们经常会定义一个数据格式,通常会包含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中定义的那样,我们得到了我们想要的消息。

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

源码可以去github <https://github.com/viyog/viboot/>或者码云
<https://gitee.com/yw18530069930/viboot>上进行下载,后续的例子都会同步更新。

云撸猫



公众号



如果帮到了你,请点击推荐让更多人看到~

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信