前言

近日心血来潮想做一个开源项目,目标是做一款可以适配多端、功能完备的模板工程,包含后台管理系统和前台系统,开发者基于此项目进行裁剪和扩展来完成自己的功能开发。本项目为前后端分离开发,后端基于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. 前端科普系列(5):ESLint - 守住优雅的护城河

    作者:Morrain [前端科普系列]帮助阅读者了解web前端,主要覆盖web前端的基础知识,但不深入讲解,定位为大而全并非细而精,适合非前端开发的同学对前端有一个系统的认识,能更好的与前端开发协作. ...

  2. vue 基于axios封装request接口请求——request.js文件

    https://blog.csdn.net/m0_67393593/article/details/123266577?utm_medium=distribute.pc_relevant.none-t ...

  3. js根据对象数组中某一属性值,合并相同项,并对某一属性累加处理

    https://www.cnblogs.com/mahao1993/p/13491430.html

  4. 《模拟龙生》|500行Go代码写一个随机冒险游戏|巨龙修为挑战开启

    一.前言 新年就要到了,祝大家新的一年: 龙行龘龘, 前程朤朤! 白泽花了点时间,用 500行 Go 代码写了一个控制台的小游戏:<模拟龙生>,在游戏中你将模拟一条新生的巨龙,开始无尽的冒 ...

  5. iview 表单有值却校验失败

    转载请注明出处: iview 表单校验数值的时候,表单有值,却在提交的时候,提示表单校验失败: 解决方案: 1. IviewUI的文档里查到了rules规则里面有个校验类型的属性字段type rule ...

  6. spring cloud 通过feign请求设置请求头

    本文为博主原创,转载请注明出处: spring cloud 服务组件之间通过feign 的方式请求,会携带很少的基础类型的消息头参数,比如Content-Type等,但不会携带自定义或指定的请求头参数 ...

  7. Java21 + SpringBoot3集成Spring Data JPA

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  8. SV OOP-1

    内容 面向对象编程 面向对象的基本概念 对象没有办法直接操作,需要通过handle(指向对象的指针)进行操作 面向对象的基本术语 面向对象的优势 类的定义 实例化对象 new() - 产生具体对象,开 ...

  9. 【TouchGFX】AnalogClock 小部件使用小记

  10. 2023年春秋杯网络安全联赛冬季赛-CRYPTO MISC WP

    浅谈:*代表未做出的,赛后复现了一下.本次题目还是挺有意思的,比赛期间做啦俩.题目有很多值得学习的东西.顺便在此记录一下.继续努力吧!! CRYPTO not_wiener(中等) 题目附件 查看代码 ...