SpringBoot中优雅地实现统一响应对象
前言
近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于Java21和SpringBoot3开发,后端使用Spring Security、JWT、Spring Data JPA等技术栈,前端提供了vue、angular、react、uniapp、微信小程序等多种脚手架工程。
项目地址:https://gitee.com/breezefaith/fast-alden
在前后端分离的项目开发过程中,我们通常会对数据返回格式进行统一的处理,这样可以方便前端人员取数据。但如果定义好响应对象R后,Controller类中每一个方法的返回值类型都只能是这个响应对象类,会使代码显得很不优雅。
@RestController
@RequestMapping("/admin")
public class AdminController {
@PostMapping(value = "/register")
public R<UmsAdmin> register(@Validated @RequestBody UmsAdminParam umsAdminParam) {
return R.success(new UmsAdmin());
}
@PostMapping(value = "/logout")
public R logout() {
return R.success(null);
}
@PostMapping(value = "/login")
public R login() {
return R.success(new UmsAdmin());
}
}
为了能够实现统一的响应对象,又能优雅的定义Controller类的方法,使其每个方法的返回值是其应有的类型,可以参考本文,主要是借助RestControllerAdvice注解和ResponseBodyAdvice接口来实现。
实现步骤
定义统一响应对象类
/**
* 响应结果类
*
* @param <T> 任意类型
*/
@Data
public class ResponseResult<T> {
/**
* 响应状态码,200是正常,非200表示异常
*/
private int status;
/**
* 异常编号
*/
private String errorCode;
/**
* 异常信息
*/
private String message;
/**
* 响应数据
*/
private T data;
public static <T> ResponseResult<T> success() {
return success(HttpServletResponse.SC_OK, null, null);
}
public static <T> ResponseResult<T> success(T data) {
return success(HttpServletResponse.SC_OK, null, data);
}
public static <T> ResponseResult<T> fail(String message) {
return fail(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null, message, null);
}
public static <T> ResponseResult<T> fail(String errorCode, String message) {
return fail(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorCode, message, null);
}
public static <T> ResponseResult<T> success(int status, String message, T data) {
ResponseResult<T> r = new ResponseResult<>();
r.setStatus(status);
r.setMessage(message);
r.setData(data);
return r;
}
public static <T> ResponseResult<T> fail(int status, String errorCode, String message) {
return fail(status, errorCode, message, null);
}
public static <T> ResponseResult<T> fail(int status, String errorCode, String message, T data) {
ResponseResult<T> r = new ResponseResult<>();
r.setStatus(status);
r.setErrorCode(errorCode);
r.setMessage(message);
r.setData(data);
return r;
}
}
定义一个忽略响应封装的注解
有些场景下我们不希望Controller方法的返回值被包装为统一响应对象,可以先定义一个忽略响应封装的注解,配合后续代码实现。
/**
* 忽略响应封装注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreRestControllerResponseAdvice {
}
实现ResponseBodyAdvice接口
本步骤需要使用@RestControllerAdvice注解,它是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice继承了@Component,因此@RestControllerAdvice本质上是个Component,用于定义@ExceptionHandler,@InitBinder和@ModelAttribute方法,适用于所有使用@RequestMapping方法。
还要用到ResponseBodyAdvice,它是Spring框架提供的一个接口,用于对Controller方法返回的响应体进行全局处理。它可以在Controller方法执行完毕并且响应体已经生成之后,对响应体进行自定义的修改或者增强操作。它本质上就是使用Spring AOP定义的一个切面,作用于Controller方法执行完成后。
具体实现代码如下:
/**
* 响应实体封装切面
*/
@RestControllerAdvice(basePackages = {"com.demo.controller"})
public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 方法没有IgnoreRestControllerResponseAdvice注解,且response不是ResponseResult类型时启用beforeBodyWrite
return !returnType.hasMethodAnnotation(IgnoreRestControllerResponseAdvice.class)
&& !returnType.getParameterType().isAssignableFrom(ResponseResult.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果返回值是void类型,直接返回200状态信息
if (returnType.getParameterType().isAssignableFrom(void.class)) {
return ResponseResult.success();
}
if (!(body instanceof ResponseResult)) {
// warning: RestController方法上返回值类型为String时,响应的Content-Type是text/plain,需要手动指定为application/json
if (body instanceof String) {
try {
return JsonUtils.toJSON(ResponseResult.success(body));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
return ResponseResult.success(body);
}
return body;
}
}
上述代码会对com.demo.controller包下所有的含有@RequestMapping注解的方法进行拦截,如果方法上没有IgnoreRestControllerResponseAdvice注解且返回值类型不是ResponseResult时,执行beforeBodyWrite方法。在beforeBodyWrite中将方法返回值包装为ResponseResult对象。
定义Controller类
下面我们就可以定义一个Controller类来进行简单的开发和测试。
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/method1")
public ResponseResult<Integer> method1() {
return ResponseResult.success(100);
}
@GetMapping("/method2")
public void method2() {
}
@GetMapping(value = "/method3")
@IgnoreRestControllerResponseAdvice
public String method3() {
return "不会被封装";
}
/**
* RestController中返回值类型是String的方法默认响应类型是text/plain,需要手动指定为application/json方可对其进行包装
*/
@GetMapping(value = "/method4", produces = MediaType.APPLICATION_JSON_VALUE)
public String method4() {
return "会被封装";
}
}
总结
本文介绍了SpringBoot项目中优雅地实现统一响应对象,如有错误,还望批评指正。
在后续实践中我也是及时更新自己的学习心得和经验总结,希望与诸位看官一起进步。
SpringBoot中优雅地实现统一响应对象的更多相关文章
- springboot中web应用的统一异常处理
在web应用中,请求处理过程中发生异常是非常常见的情况.springboot为我们提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异 ...
- 如何在SpringBoot中优雅地重试调用第三方API?
前言 作为后端程序员,我们的日常工作就是调用一些第三方服务,将数据存入数据库,返回信息给前端.但你不能保证所有的事情一直都很顺利.像有些第三方API,偶尔会出现超时.此时,我们要重试几次,这取决于你的 ...
- 解析iOS开发中的FirstResponder第一响应对象
1. UIResonder 对于C#里所有的控件(例如TextBox),都继承于Control类.而Control类的继承关系如下: 代码如下: System.Object System.Marsha ...
- SpringBoot - 参数校验、统一异常、统一响应
转载自: https://blog.csdn.net/chaitoudaren/article/details/105610962 前言 本篇主要要介绍的就是controller层的处理,一个完整的后 ...
- SpringBoot中BeanValidation数据校验与优雅处理详解
目录 本篇要点 后端参数校验的必要性 不使用Validator的参数处理逻辑 Validator框架提供的便利 SpringBoot自动配置ValidationAutoConfiguration Va ...
- Springboot中使用AOP统一处理Web请求日志
title: Springboot中使用AOP统一处理Web请求日志 date: 2017-04-26 16:30:48 tags: ['Spring Boot','AOP'] categories: ...
- SpringBoot系列(十)优雅的处理统一异常处理与统一结果返回
SpringBoot系列(十)统一异常处理与统一结果返回 往期推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列 ...
- SpringBoot中如何实现业务校验,这种方式才叫优雅!
大家好,我是飘渺. 在日常的接口开发中,为了保证接口的稳定安全,我们一般需要在接口逻辑中处理两种校验: 参数校验 业务规则校验 首先我们先看看参数校验. 参数校验 参数校验很好理解,比如登录的时候需要 ...
- SpringBoot中yaml配置对象
转载请在页首注明作者与出处 一:前言 YAML可以代替传统的xx.properties文件,但是它支持声明map,数组,list,字符串,boolean值,数值,NULL,日期,基本满足开发过程中的所 ...
- Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题
Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...
随机推荐
- 一、redis单例安装(linux)
系列导航 一.redis单例安装(linux) 二.redis主从环境搭建 三.redis集群搭建 四.redis增加密码验证 五.java操作redis 环境:centos7.5需要的安装包: re ...
- 手把手实践教你删除项目当中无用的npm包
在公司中,我们大部分都是多人共同开发和长时间维护一个项目,但是有时候我们会发现有很多已经废弃的npm 包存在 package.json 中,我们想要删除,但是又不能盲目的删除?那么 depcheck ...
- C++ Lambda 表达式递归写法
今天看到一篇博客介绍使用 Lambda 表达式递归计算 n!.使用了 C++14 的 generic lambda,给 Lambda 表达式加了一个模板参数,在函数调用的时候将 Lambda 表达式作 ...
- ClickHouse的Join算法
ClickHouse的Join算法 ClickHouse是一款开源的列式分析型数据库(OLAP),专为需要超低延迟分析查询大量数据的场景而生.为了实现分析应用可能达到的最佳性能,分析型数据库(OLAP ...
- 超全面总结Vue面试知识点,助力金三银四
前言 本文会对Vue中一些常见的重要知识点以及框架原理进行整理汇总,意在帮助作者以及读者自测Vue的熟练度以及方便查询与复习.金三银四的到来,想必vue会是很多面试官的重点考核内容,希望小伙伴们读完本 ...
- Redis之入门概括与指令
Redis特点(AP模型,优先保证可用,不会管数据丢失): 快的原因: 基于内存操作,操作不需要跟磁盘交互 k-v结构,类似与hashMap,所以查询速度非常快,接近O(1). 底层数据结构是有如:跳 ...
- [转帖]使用s3(minio)为kubernetes提供pv存储
http://www.lishuai.fun/2021/12/31/k8s-pv-s3/#/%E8%A6%81%E6%B1%82 我们可以通过csi使用s3为kubernetes提供pv存储,当我们申 ...
- [转帖] 请求量突增一下,系统有效QPS为何下降很多?
https://www.cnblogs.com/codelogs/p/17056485.html 原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介# 最近我观察到一 ...
- 【转帖】Java Full GC (Ergonomics) 的排查
文章目录 1. Full GC (Ergonomics) 1.1 Java 进程一直进行 Full GC 1.2 Full GC 的原因 1.3 检查堆占用 2. 代码检查 3. 解决方式 1. Fu ...
- [转帖]从DDR到DDR4,内存核心频率基本上就没太大的进步!
https://zhuanlan.zhihu.com/p/84194049 从2001年DDR内存面世以来发展到2019年的今天,已经走过了DDR.DDR2.DDR3.DDR4四个大的规格时代了(DD ...