springboot项目统一封装返回值和异常处理(方式一)
为什么要统一返回值:
在我们做后端应用的时候,前后端分离的情况下,我们经常会定义一个数据格式,通常会包含code
,message
,data
这三个必不可少的信息来方便我们的交流,下面我们直接来看代码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 实现统一异常处理。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南