SpringMVC利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值

在我们进行Java的Web应用开发时,如何写更少的代码,做更多的事情。如何让开发更容易上手,更专注于业务层面,不需要太关心底层的实现。这里就分享一些我平时在搭建基础框架时候的一些心得体验。

统一处理返回值
在web应用中,通常前后端会定义一个统一的对象来封装返回值,一般除了业务数据之外,可能会包含一些请求相关的数据
例如以下这个对象:

  • code来标识整个请求的结果
  • msg用于返回错误信息
  • data用于返回实际的业务数据。
{
  "code": 0,
  "msg": "success",
  "data": {}
}

统一封装的好处就是前端可以使用统一的逻辑进行请求处理,能够编写通用代码来处理返回值。

当然这也需要后端做一定的开发。通常我们都是直接写在代码里面,手动去创建一个封装对象,然后将数据set进去,或者是封装类添加一些静态方法之类的。 在大部分情况下,这些工作都是重复的。

ResponseBodyAdvice 的执行流程

今天介绍的这个接口, ResponseBodyAdvice, 这是由SpringMvc提供的一个接口,在消息转换前处理返回值,源码如下:

public interface ResponseBodyAdvice<T>{
	boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
	T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType,
			ServerHttpRequest request, ServerHttpResponse response);
}

这个接口在返回值被消息转换器写回前端之前进行处理, 大致处理流程如下:

在这里插入图片描述
我们实现这个接口的代码主要在这个方法里被调用 RequestResponseBodyAdviceChain.processBody, 可以看到这一段逻辑很简单

先执行ResponseBodyAdvice.supports看当前切面类是否支持,如果支持再调用ResponseBodyAdvice.beforeBodyWrite方法并返回

返回值会被 HttpMessageConverter.write 接口在进行最终的转换(例如转JSON),然后写回前端

private <T> Object processBody(@Nullable Object body, MethodParameter returnType, MediaType contentType,
		Class<? extends HttpMessageConverter<?>> converterType,
		ServerHttpRequest request, ServerHttpResponse response) {

	for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {
		if (advice.supports(returnType, converterType)) {
			body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType,
					contentType, converterType, request, response);
		}
	}
	return body;
}

ResponseBodyAdvice 的初始化

SpringMVC在初始化的时候, 会调用RequestMappingHandlerAdapter.initControllerAdviceCache,将ResponseBodyAdvice初始化到容器中

里面会调用ControllerAdviceBean.findAnnotatedBeans ,获取所有带有 @ControllerAdvice 注解的类

将所有实现了 ResponseBodyAdvice 接口的Bean放入requestResponseBodyAdviceBeans中, 在之前介绍到的 getAdvice() 方法取得就是该对象。

//代码片段
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
	return Arrays.stream(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class))
			.filter(name -> context.findAnnotationOnBean(name, ControllerAdvice.class) != null)
			.map(name -> new ControllerAdviceBean(name, context))
			.collect(Collectors.toList());
}

// 代码片段
for (ControllerAdviceBean adviceBean : adviceBeans) {
	Class<?> beanType = adviceBean.getBeanType();
	if (beanType == null) {
		throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
	}
	Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
	if (!attrMethods.isEmpty()) {
		this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
	}
	Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
	if (!binderMethods.isEmpty()) {
		this.initBinderAdviceCache.put(adviceBean, binderMethods);
	}
	if (RequestBodyAdvice.class.isAssignableFrom(beanType)) {
		requestResponseBodyAdviceBeans.add(adviceBean);
	}
	if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
		requestResponseBodyAdviceBeans.add(adviceBean);
	}
}

了解到这些,我们实现一个通用的返回值处理就很简单了, 只需要实现 ResponseBodyAdvice 接口,并且加上 @ControllerAdvice 注解就可以了

这是我实现的一个,统一封装返回值的实现, 大家可以参考一下,根据自己的业务需求来进行修改

package com.diamondfsd.fast.mvc.advice;

import com.diamondfsd.fast.mvc.annotations.IgnoreAware;
import com.diamondfsd.fast.mvc.entity.FastResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
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.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * 统一返回数据封装
 * @author Diamond
 */
@ControllerAdvice
public class FastMvcResponseBodyAwareAdvice implements ResponseBodyAdvice<Object> {

    private final Map<Method, Boolean> supportsCache = new WeakHashMap<>();

    private final String [] basePackages;
    private ObjectMapper objectMapper = new ObjectMapper();

    public FastMvcResponseBodyAwareAdvice(String [] basePackages) {
        this.basePackages = basePackages;
    }

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        if (supportsCache.containsKey(returnType.getMethod())) {
            return supportsCache.get(returnType.getMethod());
        }
        boolean isSupport = getIsSupport(returnType);
        supportsCache.put(returnType.getMethod(), isSupport);
        return isSupport;
    }

    private boolean getIsSupport(MethodParameter returnType) {
        Class<?> declaringClass = returnType.getMember().getDeclaringClass();

        IgnoreAware classIgnore = declaringClass.getAnnotation(IgnoreAware.class);
        IgnoreAware methodIgnore = returnType.getMethod().getAnnotation(IgnoreAware.class);
        if (classIgnore != null || methodIgnore != null || FastResult.class.equals(returnType.getGenericParameterType())) {
            return false;
        }
        for (int i = 0; i < basePackages.length; i++) {
            if (declaringClass.getPackage().getName().startsWith(basePackages[i])) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
                                  ServerHttpResponse response) {
        FastResult<Object> result = new FastResult<>();
        result.setData(body);
        if (returnType.getGenericParameterType().equals(String.class)) {
            try {
                response.getHeaders().set("Content-Type", "application/json;charset=utf-8");
                return objectMapper.writeValueAsString(result);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/208698.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python爬虫基础之Scrapy框架详解

目录 1. 简介2. Scrapy的安装3. Scrapy的架构4. Scrapy的数据流程5. Scrapy开发流程5.1 创建项目5.2 创建Spider5.3 创建Item5.4 编写Spider5.5 运行Spider 参考文献 原文地址&#xff1a;https://program-park.top/2023/12/01/reptile_5/ 本文章中所有内容仅供学习交流使用&am…

8、内部FLASH模拟EEPROM实验(STM32F407)

STM32编程方式 在线编程&#xff08;ICP,In-Circuit Programming&#xff09;: 通过JTAG/SWD协议或者系统加载程序(Bootloader)下载用户应用程序到微控制器中。 在程序中编程(IAP,In Application Programming):通过任何一种通信接口(如IO端口,USB,CAN,UART,I2C,SPI等)下载程…

2024 年综合网络安全审计清单

在网络威胁不断演变的时代&#xff0c;确保组织数据和系统的安全至关重要。 全面的网络安全审核清单可以在实现这一目标方面发挥关键作用。但是&#xff0c;什么才是有效的网络安全审核清单呢&#xff1f;以及如何对其进行定制以满足您组织的独特需求&#xff1f; 了解网络安…

【设计模式-2.3】创建型——原型模式

说明&#xff1a;本文介绍设计模式中&#xff0c;创建型中的原型模式&#xff1b; 飞机大战 创建型设计模式关注于对象的创建&#xff0c;原型模式也不例外。如简单工厂和工厂模式中提到过的飞机大战这个例子&#xff0c;游戏中飞机、坦克对象会创建许许多多的实例&#xff0…

水利安全监测方案——基于RTU200的解决方案

引言&#xff1a; 水资源是人类赖以生存的重要基础&#xff0c;对于保障水利系统安全运行以及应对自然灾害起着关键作用。为了实现水利安全监测的目标&#xff0c;我们提出了基于RTU200的解决方案。本方案将结合RTU200的可靠性、灵活性和高效性&#xff0c;为您打造一个全面的…

vue使用实现录音功能js-audio-recorder

前言 最近项目中需要实现一个录音上传功能&#xff0c;用于语音评论可以上录音。 下载插件&#xff1a; npm i js-audio-recorder完整代码 <template><div style"padding: 20px;"><h3>录音上传</h3><div style"font-size:14px"…

[Java学习日记]网络编程

目录 一.常见的软件架构、网络编程三要素、IP 二.利用UDP发送与接收数据 三.改聊天室 四.组播案例 五.TCP通信案例 一.常见的软件架构、网络编程三要素、IP 网络编程&#xff1a;在网络通信协议下&#xff0c;不同的计算机上运行的程序进行的数据传输 在Java中可以使用java…

Promise的resolve和reject方法(手写题)

1.resolve 2.reject 3.手写 1.resolve //构造函数上添加 resolve 方法 Promise.resolve function (value) {return new Promise((resolve, reject) > {if (value instanceof Promise) {value.then((val) > {resolve(val)},(err) > {reject(err)})} else {resolve(v…

springboot项目中注入bean后,调用时报n

需求&#xff1a; 在socket接收到上报数据后&#xff0c;在handler中调用工具类中ProtocolAnalyse的conAnalyse(byte[] data, int dataLen)解析数据。解析数据后&#xff0c;将解析后的结果保存至数据库。注入了三个bean&#xff1a; Autowiredprivate PersonTeService person…

基于融合正余弦和柯西变异的麻雀优化算法(SCSSA)-CNN-BiLSTM(双向长短期记忆网络)的时间序列预测模型

融合正余弦和柯西变异的麻雀优化算法&#xff08;SCSSA&#xff09;原理如下&#xff1a; ①采用折射反向学习策略初始化麻雀算法个体&#xff0c;基本思想是通过计算当前解的反向解来扩大搜索范围&#xff0c;借此找出给定问题更好的备选解&#xff1b;②采用正余弦策略替换原…

2023年建筑轮廓高度数据技术服务

在之前的文章【数据分享】全国62个城市建筑轮廓数据带层数&#xff08;2018年&#xff09;和【数据分享】全国77个城市建筑轮廓数据带高度&#xff08;2019年&#xff09;里我们分别分享了2018年和2019年2020年2021年2022年的建筑轮廓数据&#xff0c;数据年份也有些老了&#…

react实现加载动画

1.Spinning.tsx import "./Spinning.scss";interface Props {isLoading: boolean;children?: React.ReactNode; }const Spinning: React.FC<Props> ({isLoading true,children }) > {return <div className{spinning-wrapper${isLoading ? " l…

Hdoop学习笔记(HDP)-Part.2 核心组件原理

二、核心组件原理 1.分布式协调ZooKeeper (1)应用场景 使用分布式系统就无法避免对节点管理的问题&#xff08;需要实时感知节点的状态、对节点进行统一管理等&#xff09;&#xff0c;而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性&#xff0c;ZooKeeper作为一个…

5.C转python

新始: 13.列表可被改变(数据),元组不可被改变(数据),二者皆与C中的数组的大致相同 14.创建列表方法: 1.一个[ ]就是一个空的列表 2.使用list函数来创建列表 如: 15.可以在[ ]内部指定列表的初始值,打印方法: 如: 16.在python中,在同一个列表中,可以放不同类型的变量(也可…

【零基础入门Docker】Dockerfile中的USER指令以及dockerfile命令详解

✍面向读者&#xff1a;所有人 ✍所属专栏&#xff1a;Docker零基础入门专栏 目录 第 1 步&#xff1a;创建 Dockerfile 第 2 步&#xff1a;构建 Docker 镜像 第 3 步&#xff1a;运行 Docker 容器 第 4 步&#xff1a;验证输出 dockerfile命令详解 最佳实践 默认情况下…

oops-framework框架 之 初始了解(一)

引擎&#xff1a;CocosCreator 环境&#xff1a; Mac Gitee: oops-framework 简介 oops-framework是由作者dgflash编写&#xff0c;基于CocosCreator 3.x而实现的开源框架。 该框架以插件形式存在&#xff0c;主要目的是为了降低与项目的耦合&#xff0c;并且通过插件内部的…

【LeetCode:1657. 确定两个字符串是否接近 | 计数 + 阅读理解】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

若依前后端分离版idea启动

1.项目地址 https://gitee.com/y_project/RuoYi-Vue2.修改配置 修改数据库配置 3.执行sql 4.启动项目 启动成功 5.启动前端 首先要安装nodejs,版本不能太高,本人用的nodejs11版本的 npm install --registry https://registry.npm.taobao.orgnpm run dev启动成功 访问 h…

态路小课堂丨InfiniBand与以太网:AI时代的网络差异

近年来&#xff0c;随着人工智能技术的迅猛发展&#xff0c;以ChatGPT为代表的大型AI模型引起了广泛关注。然而&#xff0c;在这个AI时代&#xff0c;我们不得不重新审视传统的以太网和基于InfiniBand构建的网络之间的区别。尤其是今年以来&#xff0c;InfiniBand因其在AI领域中…

CRM:如何通过客户数据分析获得业务洞察?

客户数据分析&#xff0c;也称客户分析&#xff0c;是收集、组织和分析客户数据&#xff0c;以深入了解客户行为的过程。企业将利用这些数据来制定与营销、产品开发、销售等相关的业务决策。 通过客户分析&#xff0c;你可以做出简单的业务决策&#xff0c;比如找出投资回报率…