使用Spring AOP 和自定义注解统一API返回值格式
摘要:统一接口返回值格式后,可以提高项目组前后端的产出比,降低沟通成本。因此,在借鉴前人处理方法的基础上,通过分析资料,探索建立了一套使用Spring AOP和自定义注解无侵入式地统一返回数据格式的方法。
§前言
我们封装所有的Controller中接口返回结果,将其处理为统一返回数据结构后,可以提高前后端对接效率,降低沟通成本。而使用Spring AOP和自定义注解无侵入式地统一返回数据格式,则可以避免在每一个api上都处理返回格式,从而使后端开发节约开发时间,更加专注于开发业务逻辑。
后端返回给前端的自定义返回格式如下:
{
"code":200,
"message":"OK",
"data":{
}
}
其中的三个参数的含义如下:
- code: 返回状态码;
- message: 返回信息的描述;
- data: 返回值。
§直接修改API返回值类型
Spring AOP和自定义注解的基本概念可以分别参考《Spring AOP 面向切面编程之AOP是什么》和《Spring注解之自定义注解入门》。下面定义返回数据格式的封装类:
package com.eg.wiener.config.result;
import lombok.Getter;
import lombok.ToString;
import java.io.Serializable;
@Getter
@ToString
public class ResultData<T> implements Serializable {
private static final long serialVersionUID = 4890803011331841425L;
/** 业务错误码 */
private Integer code;
/** 信息描述 */
private String message;
/** 返回参数 */
private T data;
private ResultData(ResultStatus resultStatus, T data) {
this.code = resultStatus.getCode();
this.message = resultStatus.getMessage();
this.data = data;
}
/** 业务成功返回业务代码和描述信息 */
public static ResultData<Void> success() {
return new ResultData<Void>(ResultStatus.SUCCESS, null);
}
/** 业务成功返回业务代码,描述和返回的参数 */
public static <T> ResultData<T> success(T data) {
return new ResultData<T>(ResultStatus.SUCCESS, data);
}
/** 业务成功返回业务代码,描述和返回的参数 */
public static <T> ResultData<T> success(ResultStatus resultStatus, T data) {
if (resultStatus == null) {
return success(data);
}
return new ResultData<T>(resultStatus, data);
}
/** 业务异常返回业务代码和描述信息 */
public static <T> ResultData<T> failure() {
return new ResultData<T>(ResultStatus.INTERNAL_SERVER_ERROR, null);
}
/** 业务异常返回业务代码,描述和返回的参数 */
public static <T> ResultData<T> failure(ResultStatus resultStatus) {
return failure(resultStatus, null);
}
/** 业务异常返回业务代码,描述和返回的参数 */
public static <T> ResultData<T> failure(ResultStatus resultStatus, T data) {
if (resultStatus == null) {
return new ResultData<T>(ResultStatus.INTERNAL_SERVER_ERROR, null);
}
return new ResultData<T>(resultStatus, data);
}
}
=== 我是分割线 ===
package com.eg.wiener.config.result;
import lombok.Getter;
import lombok.ToString;
import org.springframework.http.HttpStatus;
@ToString
@Getter
public enum ResultStatus {
SUCCESS(HttpStatus.OK.value(), "OK"),
BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "Bad Request"),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Internal Server Error"),
TOO_MANY_REQUESTS(HttpStatus.TOO_MANY_REQUESTS.value(), "请勿重复请求"),
USER_NOT_FIND(-99, "请登陆");
/**
* 业务状态码
*/
private Integer code;
/**
* 业务信息描述
*/
private String message;
ResultStatus(Integer code, String message) {
this.code = code;
this.message = message;
}
}
在UserController类中添加测试函数,其返回值直接使用上面定义的ResultData:
/**
* @author Wiener
*/
@RestController
@RequestMapping("/user")
public class UserController {
private static Logger logger = LoggerFactory.getLogger(UserController.class);
/**
* 示例地址 xxx/user/getResultById?userId=1090330
*
* @author Wiener
*/
@GetMapping(value ="/getResultById", produces = "application/json; charset=utf-8")
public ResultData getResultById(Long userId) {
User user = new User();
user.setId(userId);
user.setAddress("测试地址是 " + UUID.randomUUID().toString());
logger.info(user.toString());
return ResultData.success(user);
}
}
启动项目,在浏览器中输入URL,得到如下结果:
{
"code":200,
"message":"OK",
"data":{
"id":1090330,
"userName":null,
"age":null,
"address":"测试地址是 f057181c-e7e2-41ec-9066-0e72619f0f86",
"mobilePhone":null
}
}
说明已经成功定义返回数据格式,但是,这里并没有使用自定义注解和Spring AOP这两个知识点。客官莫急,待我喝完这杯咖啡,且听我娓娓道来。
§设置全局返回值类型
下面首先定义一个用于设置全局切入点的自定义注解@ResponseResultBody。参考文献中强调,此自定义注解需要添加@ResponseBody注解,这是画蛇添足,下文的测试用例将给大家演示不添加也可以达到同样的目的。
package com.eg.wiener.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ResponseResultBody {
}
通过实现ResponseBodyAdvice接口,基于上述自定义注解@ResponseResultBody获取切面,封装返回值格式:
package com.eg.wiener.config.result;
import com.eg.wiener.aop.ResponseResultBody;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.annotation.Annotation;
@RestControllerAdvice
public class ResultBodyAdvice implements ResponseBodyAdvice<Object> {
private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseResultBody.class;
/**
* 基于自定义注解判断类或者方法是否使用了注解 @ResponseResultBody,使用就拦截
* @return boolean. true:执行函数beforeBodyWrite;否则,不执行
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE)
|| returnType.hasMethodAnnotation(ANNOTATION_TYPE);
}
/**
* 当类或者方法使用了 @ResponseResultBody 就会调用这个方法
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 避免重复封装响应报文
if (body instanceof ResultData) {
return body;
}
// 使用约定全局返回格式封装响应报文
return ResultData.success(body);
}
}
修改测试用例,添加自定义注解@ResponseResultBody:
/**
* @author Wiener
*/
@RestController
@RequestMapping("/user")
@ResponseResultBody
public class UserController {
private static Logger logger = LoggerFactory.getLogger(UserController.class);
* 示例地址 /user/getUserTestById?userId=1090330
*
* @author Wiener
*/
@GetMapping(value ="/getUserTestById", produces = "application/json; charset=utf-8")
public User getUserTestById(Long userId) throws Exception {
User user = new User();
user.setId(userId);
user.setAddress("测试地址是 " + UUID.randomUUID().toString());
logger.info(user.toString());
return user;
}
/**
* /user/getResult?userId=1090330
* @param user
* @return
*/
@PostMapping(value ="/getResult", produces = "application/json; charset=utf-8")
public ResultData getResult(@RequestBody User user) {
user.setAddress("测试地址是 " + UUID.randomUUID().toString());
logger.info(user.toString());
return ResultData.success(user);
}
}
启动服务,在Postman中请求getResult函数,可以得到如下结果:

在浏览器中请求API getUserTestById,可以得到如下结果:

说明成功统一返回值格式。
§小结
本篇文章的内容基本上就是这些,我们来总结一下。在控制层添加自定义注解@ResponseResultBody后,我们就可以通过Spring AOP定义基于此注解的切面。如果一个代理类使用了此注解,那么就封装其返回值;否则,不处理。
§Reference
使用Spring AOP 和自定义注解统一API返回值格式的更多相关文章
- .NetCore Web Api 利用ActionFilterAttribute统一接口返回值格式
.Net Core 同 Asp.Net MVC一样有几种过滤器,这里不再赘述每个过滤器的执行顺序与作用. 在实际项目开发过程中,统一API返回值格式对前端或第三方调用将是非常必要的,在.NetCore ...
- 只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常
## 统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生. 比较通用的返回值格式如下: ```jav ...
- spring AOP 和自定义注解进行身份验证
一个SSH的项目(springmvc+hibernate),需要提供接口给app使用.首先考虑的就是权限问题,app要遵循极简模式,部分内容无需验证,用过滤器不能解决某些无需验证的方法 所以最终选择用 ...
- 利用Spring AOP和自定义注解实现日志功能
Spring AOP的主要功能相信大家都知道,日志记录.权限校验等等. 用法就是定义一个切入点(Pointcut),定义一个通知(Advice),然后设置通知在该切入点上执行的方式(前置.后置.环绕等 ...
- 自定义统一api返回json格式(app后台框架搭建三)
在统一json自定义格式的方式有多种:1,直接重写@reposeBody的实现,2,自定义一个注解,自己去解析对象成为json字符串进行返回 第一种方式,我就不推荐,想弄得的话,可以自己去研究一下源码 ...
- Spring Aop 修改目标方法参数和返回值
一.新建注解 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Document ...
- Spring aop 拦截自定义注解+分组验证参数
import com.hsq.common.enums.ResponseState;import com.hsq.common.response.ResponseVO;import org.aspec ...
- Spring Boot实现自定义注解
在Spring Boot项目中可以使用AOP实现自定义注解,从而实现统一.侵入性小的自定义功能. 实现自定义注解的过程也比较简单,只需要3步,下面实现一个统一打印日志的自定义注解: 1. 引入AOP依 ...
- 运用Spring Aop,一个注解实现日志记录
运用Spring Aop,一个注解实现日志记录 1. 介绍 我们都知道Spring框架的两大特性分别是 IOC (控制反转)和 AOP (面向切面),这个是每一个Spring学习视频里面一开始都会提到 ...
- ASP.NET Core搭建多层网站架构【11-WebApi统一处理返回值、异常】
2020/02/01, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[11-WebApi统一处理返回值.异常] 使用I ...
随机推荐
- 在php类中使用函数
好久没更新了,今天复习歪麦编写php框架的文章,看到几个函数,在类中使用时,当参数需要调用类的方法时,都用数组的方式传参. 1. spl_autoload_register(array($this, ...
- C# 泛型类型约束 where
1 class Program { 2 static void Main(string[] args) { 3 4 } 5 } 6 7 interface IMyInterface { 8 void ...
- httprunner 断言报错 expect_value 和check_value类型不一致
csv 源码 drillCode,drillName,code,msg y500,牙轮500,200,操作成功 YL201,牙轮201,500,牙轮编号已存在 运行后报错 E httprunner.e ...
- 大模型评测之幻觉检测hallucination_evaluation_model
大背景: 2025开年deepseek铺天盖地的新闻 参会代表已经表明,年度主线就是以AI为基础 Manus于3月初横空出世 国内各种模型竞赛的现状,只要是和科技沾边的公司不可能没有大模型,哪怕是里三 ...
- npm ERR! request to https://registry.npm.taobao.org/axios failed, reason: certificate has expired
前言 一直使用 npm build没问题的,突然出现报错: npm WARN install Usage of the `--dev` option is deprecated. Use `--onl ...
- 基于SLAM系统建图仿真,完成定位仿真
博客地址:https://www.cnblogs.com/zylyehuo/ 基于SLAM系统完成建图仿真,详见之前的博客 基于Gazebo搭建移动机器人,并结合SLAM系统完成建图仿真 - zyly ...
- 在Java集合框架中,`Set`接口是一个重要的接口,它表示一个不包含重复元素的集合。常见的`Set`实现类有`HashSet`、`LinkedHashSet`和`TreeSet`。下面是关于`Set`接口的一些基本用法和方法介绍:
常用实现类 HashSet: 基于哈希表实现,元素无序. 插入.删除.查找操作的时间复杂度为O(1). LinkedHashSet: 继承自HashSet,并使用双向链表来维护元素的插入顺序. 保留元 ...
- 【软件】在Windows和Ubuntu上使用TFTP和NFS
在Windows和Ubuntu上使用TFTP和NFS 零.介绍 最近在玩Linux开发板,在开发的过程中发现需要用到tftp和nfs来帮助传输文件,故此记录如何使用这两种软件. TFTP(Trivia ...
- 使用Python建模量子隧穿
引言 量子隧穿是量子力学中的一个非常有趣且令人神往的现象.在经典物理学中,我们通常认为粒子必须克服一个势垒才能通过它.但是,在量子力学中,粒子有时可以"穿越"一个势垒,即使它的能量 ...
- AOP流程及原理
目录 一.AOP结构介绍 @Pointcut 通知 原理 连接点 拦截器 二.Bean介入点 EnableAspectJAutoProxy AspectJAutoProxyRegistrar Anno ...