前言

近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于Java21SpringBoot3开发,后端使用Spring SecurityJWTSpring Data JPA等技术栈,前端提供了vueangularreactuniapp微信小程序等多种脚手架工程。

项目地址: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中优雅地实现统一响应对象的更多相关文章

  1. springboot中web应用的统一异常处理

    在web应用中,请求处理过程中发生异常是非常常见的情况.springboot为我们提供了一个默认的映射:/error,当处理中抛出异常之后,会转到该请求中处理,并且该请求有一个全局的错误页面用来展示异 ...

  2. 如何在SpringBoot中优雅地重试调用第三方API?

    前言 作为后端程序员,我们的日常工作就是调用一些第三方服务,将数据存入数据库,返回信息给前端.但你不能保证所有的事情一直都很顺利.像有些第三方API,偶尔会出现超时.此时,我们要重试几次,这取决于你的 ...

  3. 解析iOS开发中的FirstResponder第一响应对象

    1. UIResonder 对于C#里所有的控件(例如TextBox),都继承于Control类.而Control类的继承关系如下: 代码如下: System.Object System.Marsha ...

  4. SpringBoot - 参数校验、统一异常、统一响应

    转载自: https://blog.csdn.net/chaitoudaren/article/details/105610962 前言 本篇主要要介绍的就是controller层的处理,一个完整的后 ...

  5. SpringBoot中BeanValidation数据校验与优雅处理详解

    目录 本篇要点 后端参数校验的必要性 不使用Validator的参数处理逻辑 Validator框架提供的便利 SpringBoot自动配置ValidationAutoConfiguration Va ...

  6. Springboot中使用AOP统一处理Web请求日志

    title: Springboot中使用AOP统一处理Web请求日志 date: 2017-04-26 16:30:48 tags: ['Spring Boot','AOP'] categories: ...

  7. SpringBoot系列(十)优雅的处理统一异常处理与统一结果返回

    SpringBoot系列(十)统一异常处理与统一结果返回 往期推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列 ...

  8. SpringBoot中如何实现业务校验,这种方式才叫优雅!

    大家好,我是飘渺. 在日常的接口开发中,为了保证接口的稳定安全,我们一般需要在接口逻辑中处理两种校验: 参数校验 业务规则校验 首先我们先看看参数校验. 参数校验 参数校验很好理解,比如登录的时候需要 ...

  9. SpringBoot中yaml配置对象

    转载请在页首注明作者与出处 一:前言 YAML可以代替传统的xx.properties文件,但是它支持声明map,数组,list,字符串,boolean值,数值,NULL,日期,基本满足开发过程中的所 ...

  10. Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题

    Dubbo源码学习--优雅停机原理及在SpringBoot中遇到的问题 相关文章: Dubbo源码学习文章目录 前言 主要是前一阵子换了工作,第一个任务就是解决目前团队在 Dubbo 停机时产生的问题 ...

随机推荐

  1. 一、redis单例安装(linux)

    系列导航 一.redis单例安装(linux) 二.redis主从环境搭建 三.redis集群搭建 四.redis增加密码验证 五.java操作redis 环境:centos7.5需要的安装包: re ...

  2. 手把手实践教你删除项目当中无用的npm包

    在公司中,我们大部分都是多人共同开发和长时间维护一个项目,但是有时候我们会发现有很多已经废弃的npm 包存在 package.json 中,我们想要删除,但是又不能盲目的删除?那么 depcheck ...

  3. C++ Lambda 表达式递归写法

    今天看到一篇博客介绍使用 Lambda 表达式递归计算 n!.使用了 C++14 的 generic lambda,给 Lambda 表达式加了一个模板参数,在函数调用的时候将 Lambda 表达式作 ...

  4. ClickHouse的Join算法

    ClickHouse的Join算法 ClickHouse是一款开源的列式分析型数据库(OLAP),专为需要超低延迟分析查询大量数据的场景而生.为了实现分析应用可能达到的最佳性能,分析型数据库(OLAP ...

  5. 超全面总结Vue面试知识点,助力金三银四

    前言 本文会对Vue中一些常见的重要知识点以及框架原理进行整理汇总,意在帮助作者以及读者自测Vue的熟练度以及方便查询与复习.金三银四的到来,想必vue会是很多面试官的重点考核内容,希望小伙伴们读完本 ...

  6. Redis之入门概括与指令

    Redis特点(AP模型,优先保证可用,不会管数据丢失): 快的原因: 基于内存操作,操作不需要跟磁盘交互 k-v结构,类似与hashMap,所以查询速度非常快,接近O(1). 底层数据结构是有如:跳 ...

  7. [转帖]使用s3(minio)为kubernetes提供pv存储

    http://www.lishuai.fun/2021/12/31/k8s-pv-s3/#/%E8%A6%81%E6%B1%82 我们可以通过csi使用s3为kubernetes提供pv存储,当我们申 ...

  8. [转帖] 请求量突增一下,系统有效QPS为何下降很多?

    https://www.cnblogs.com/codelogs/p/17056485.html 原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介# 最近我观察到一 ...

  9. 【转帖】Java Full GC (Ergonomics) 的排查

    文章目录 1. Full GC (Ergonomics) 1.1 Java 进程一直进行 Full GC 1.2 Full GC 的原因 1.3 检查堆占用 2. 代码检查 3. 解决方式 1. Fu ...

  10. [转帖]从DDR到DDR4,内存核心频率基本上就没太大的进步!

    https://zhuanlan.zhihu.com/p/84194049 从2001年DDR内存面世以来发展到2019年的今天,已经走过了DDR.DDR2.DDR3.DDR4四个大的规格时代了(DD ...