摘要:统一接口返回值格式后,可以提高项目组前后端的产出比,降低沟通成本。因此,在借鉴前人处理方法的基础上,通过分析资料,探索建立了一套使用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返回值格式的更多相关文章

  1. .NetCore Web Api 利用ActionFilterAttribute统一接口返回值格式

    .Net Core 同 Asp.Net MVC一样有几种过滤器,这里不再赘述每个过滤器的执行顺序与作用. 在实际项目开发过程中,统一API返回值格式对前端或第三方调用将是非常必要的,在.NetCore ...

  2. 只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常

    ## 统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生. 比较通用的返回值格式如下: ```jav ...

  3. spring AOP 和自定义注解进行身份验证

    一个SSH的项目(springmvc+hibernate),需要提供接口给app使用.首先考虑的就是权限问题,app要遵循极简模式,部分内容无需验证,用过滤器不能解决某些无需验证的方法 所以最终选择用 ...

  4. 利用Spring AOP和自定义注解实现日志功能

    Spring AOP的主要功能相信大家都知道,日志记录.权限校验等等. 用法就是定义一个切入点(Pointcut),定义一个通知(Advice),然后设置通知在该切入点上执行的方式(前置.后置.环绕等 ...

  5. 自定义统一api返回json格式(app后台框架搭建三)

    在统一json自定义格式的方式有多种:1,直接重写@reposeBody的实现,2,自定义一个注解,自己去解析对象成为json字符串进行返回 第一种方式,我就不推荐,想弄得的话,可以自己去研究一下源码 ...

  6. Spring Aop 修改目标方法参数和返回值

    一.新建注解 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Document ...

  7. Spring aop 拦截自定义注解+分组验证参数

    import com.hsq.common.enums.ResponseState;import com.hsq.common.response.ResponseVO;import org.aspec ...

  8. Spring Boot实现自定义注解

    在Spring Boot项目中可以使用AOP实现自定义注解,从而实现统一.侵入性小的自定义功能. 实现自定义注解的过程也比较简单,只需要3步,下面实现一个统一打印日志的自定义注解: 1. 引入AOP依 ...

  9. 运用Spring Aop,一个注解实现日志记录

    运用Spring Aop,一个注解实现日志记录 1. 介绍 我们都知道Spring框架的两大特性分别是 IOC (控制反转)和 AOP (面向切面),这个是每一个Spring学习视频里面一开始都会提到 ...

  10. ASP.NET Core搭建多层网站架构【11-WebApi统一处理返回值、异常】

    2020/02/01, ASP.NET Core 3.1, VS2019 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[11-WebApi统一处理返回值.异常] 使用I ...

随机推荐

  1. 如何学好.net core?

    https://www.zhihu.com/question/348740859/answer/842656513

  2. VMware15.5虚拟机下载及安装

    一.VMware虚拟机介绍 VMWare虚拟机软件是一个"虚拟PC"软件,它使你可以在一台机器上同时运行二个或更多Windows.DOS.LINUX系统.与"多启动&qu ...

  3. AI 智能体引爆开源社区「GitHub 热点速览」

    最近很火的 Manus 智能体是一款将你的想法转化为行动的工具,能够处理生活中的各种任务.一经发布便迅速走红,并间接引爆了开源社区. 这也导致上榜的全是 AI 智能体开源项目,比如无需邀请码的开源版 ...

  4. Qt Oracle往数据库里插入或者更新图片

    文章目录 Qt Oracle往数据库里插入或者更新图片 前言 读取本地图片文件 QPixmap 转 QByteArray 组成SQL,并执行 Qt Oracle往数据库里插入或者更新图片 前言 最近遇 ...

  5. mac强制关闭程序

    使用快捷键:Command+Option+Esc 来打开"强制退出应用程序"的窗口,然后选中你需要退出的程序,再点右下方的"强制退出"即可.

  6. oracle审计详解

    Oracle使用大量不同的审计方法来监控使用何种权限,以及访问哪些对象.审计不会防止使用这些权限,但可以提供有用的信息,用于揭示权限的滥用和误用. 下表中总结了Oracle数据库中不同类型的审计. 审 ...

  7. 继承中构造方法访问特点--java 进阶day01

    1.子类不可以继承父类的构造方法 构造方法的名称必须与类名一致,上图中类名是Zi,而构造方法名是Fu,肯定不行 2.子类在初始化之前,需要对父类初始化 子类在初始化的过程中,很有可能会调用到父类的数据 ...

  8. 使用Nginx反向代理本地服务(无固定公网IP通过端口映射公开的服务)的坑

    使用Nginx反向代理本地服务(无固定公网IP通过端口映射公开的服务)的坑 前言:之前公司的服务器都是云服务器,性能比较差,而我们有一些内部使用的系统和极少数外部用户使用的系统,对资源有一定的要求,也 ...

  9. Linux C线程读写锁深度解读 | 从原理到实战(附实测数据)

    Linux C线程读写锁深度解读 | 从原理到实战(附实测数据) 读写锁练习:主线程不断写数据,另外两个线程不断读,通过读写锁保证数据读取有效性. 代码实现如下: #include <stdio ...

  10. C++ 程序员入门需要多久,怎样才能学好?

    一.我的C++学习之路:一个嵌入式老兵的自白 先交代一下我的背景:理工科毕业,半路出家学的编程.大学时代是机械专业,但阴差阳错进了一家电子公司,被分配做嵌入式开发,于是硬着头皮自学了C语言和单片机,后 ...