使用SpringAOP的方式修改controller接口返回的数据

news2024/9/22 13:33:23

1为什么需要修改返回接口的数据?

先看一个关于返回接口数据中包含时间的接口,如下接口中的birth属性,是日期,假设我们不做任何处理,那么在页面,我们看到的将是如下的时间显示效果,这明显不是我们想要的。
在这里插入图片描述
最Low B的方式,直接将birth属性变成字符串格式,然后将时间格式化成字符串
在这里插入图片描述
当然上面这种代码,要是写出来的话,可以考虑换行业了。我们都知道,在MVC中,有个Json转换器,这个Json转换器可以将我们controller返回的对象格式化成Json。
因此我们可以在格式化的时候,指定时间格式。这种方式明显比第一种方式要友好多。
在这里插入图片描述
但是这种方式也存在一个问题,那就是假设如果我们某个时间的属性,开发人员在开发时,忘记了加上对应Json包的时间格式化注解,比如这里采用的com.fasterxml.jackson.annotation.JsonFormat这个注解。那么就可能就造成没有添加这个注解的时间字段,在返回时间时,时间格式不是我们想要的。

所以,针对上述这个问题,我们还有解决方案,那就是采用如下配置,指定jackson在进行时格式化时,遇到时间类型的,将其格式为指定的格式。这种方式的好处就是,所有的日期类型的属性在返回时,MVC的Json解析器,都会将其格式化。因此不会存在遗漏注解的情况。
在这里插入图片描述

2利用AOP的思想改写controller的返回值

在来看一个例子,如下是一个正常查询返回数据时的接口。
在这里插入图片描述
那如果查询的时候,本身就没有数据,那么此时返回的数据就是如下这样的。
在这里插入图片描述
笔者认为,这种一种比较合理的方式。
但是有一种不合理的方式,就是有些接口会返回如下的报文
在这里插入图片描述
这两种方式区别在于,第一种返回空集合,第二种返回null,相信有点开发规模的团队,都有明确的开发规范,是禁止如下代码形式的,因为写方法返回了null,及其造成调用者使用的时候出现空指针异常。

	public String getXxxxx() {
		//逻辑处理
		return null;
	}
	public List<String> getListXxxx(){
		//逻辑处理
		return null;
	}

规范的代码应该如下所示

	public String getXxxxx() {
		String res = "";
		//逻辑处理
		return res;
	}
	public List<String> getListXxxx(){
		List<String> list = new ArrayList<>();
		//逻辑处理
		return list;
	}

当调用者,调用第一种方法的时候,不得不添加一堆判断空指针的代码
例如如下所示,这样增加了代码的冗余量。

String xxxxx = getXxxxx();
if(xxxxx != null) {
	xxxxx.equals("abcdefg");
}
List<String> listXxxx = getListXxxx();
if(listXxxx != null) {
	listXxxx.forEach(temp->{
		System.out.println(temp);
	});
}

而第二种方式,我们代码则可以省略一系列的null判断,因为此时,这些方法不会返回null

String xxxxx = getXxxxx();
xxxxx.equals("abcdefg");
List<String> listXxxx = getListXxxx();
listXxxx.forEach(temp->{
	System.out.println(temp);
});

最常见的例子,就是目前我们使用的一系列ORM框架,例如Mybatis(Mybatis-plus),在进行相关list查询的时候,就不会返回null,在查询不到数据的时候,就会返回空集合,而不是null。

但是每个项目组的开发人员,技能素质不一,难免有人返回值就是返回null,为了避免这个问题就是,能不能像我们修改返回数据中的日期类型一样嗯?以防止部分不遵守开发规范的人,返回不符合我们预期管理的数据嗯?

本例子中,我们就两种方式来处理

  1. 方式1:Json格式化时,将null格式为空字符串
  2. 方式2:结合AOP,获取controller方法返回值,构造初始值

方式1:
这种方式简单粗暴,就是Json格式化以后,遇到null,就把null设置为空字符串。缺点就是,因为是null,所以无法得知null之前的数据类型是什么,
例如,如下数据类型,在转Json之后,都是null,无法得知null之前是什么类型

	String aaa = null;
	Integer bbb = null;
	List<Integer> ccc = null;
	Map<String,List<Integer>> ddd = null;
	Date fff = null;

具体代码如下

/**
 3. @description:将Json中的null节点,变成空字符串
 4. @author:hutao
 5. @mail:hutao1@epri.sgcc.com.cn
 6. @date:2023年4月12日 上午10:50:56
 */
@SpringBootConfiguration
public class JacksonConfig {
 
    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                jsonGenerator.writeString("");
            }
        });
        return objectMapper;
    }
}

方式2:
这种方式利用AOP返回值,可以根据方法的返回值的,去推算出null之前的数据类型是什么。

2.1拦截controller方法

例如,我们要拦截如下的方法

@ApiOperation(value = "按照条件进行查询仿真任务表")
@PostMapping("/info/list")
public R<List<SgSimTaskB>> listTaskInfoByCondition(@RequestBody ConditionQuery conditionQuery) {
}

2.2设置拦截切入点

本案例中,使用了OpenAPI(swagger3),因此我们可以利用Swagger的注解来进行切入

//切入OpenAPI接口
@Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
public void pointcut() {	
}

2.3环绕通知改写结果

  1. ProceedingJoinPoint
@Around("pointcut()")
public Object modifyResult(ProceedingJoinPoint joinPoint) throws Throwable {
	Object proceed = joinPoint.proceed();
	if(proceed instanceof R) {
		R<?> result = (R<?>) proceed;
		//如果data数据为null,则去获取返回data数据方法的返回值类型
		if(result.getData() == null) {
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			Type type = signature.getMethod().getGenericReturnType();
			System.out.println(type);
			//com.plan.map.config.query.R<java.util.List<com.plan.map.module.test.entity.SgTmsNeB>>
			ParameterizedType parameterizedType = (ParameterizedType) type;
			Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
			for (Type t : actualTypeArguments) {
				System.out.println(t.getTypeName());
				//java.util.List<com.plan.map.module.test.entity.SgTmsNeB>
				if(t.getTypeName().contains("java.util.List")) {
					R<List<?>> reList = new R<>();
					BeanUtils.copyProperties(result, reList);
					reList.setData(new ArrayList<>());
					return reList;
				}
				//其他默认值处理
			}
		}
	}
	return proceed;
}

完整代码配置

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.SpringBootConfiguration;

import com.plan.map.config.query.R;

/**
 * @description:使用AOP修改返回值结果
 * @author:hutao
 * @mail:hutao1@epri.sgcc.com.cn
 * @date:2023年4月11日 上午10:55:14
 */
@Aspect
@SpringBootConfiguration
public class ModifyResultConfig {
	
	@Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
	public void pointcut() {
		
	}
	/**
	 * @description:修改返回参数中的Data为null的时候,为其初始化默认值
	 * @author:hutao
	 * @mail:hutao1@epri.sgcc.com.cn
	 * @date:2023年4月11日 上午10:55:32
	 */
	//注意:使用@AfterReturning只能修改基本数据类型,不能修改引用类型,即使new一个新对象返回,一样不生效
	@Around("pointcut()")
	public Object modifyResult(ProceedingJoinPoint joinPoint) throws Throwable {
		Object proceed = joinPoint.proceed();
		if(proceed instanceof R) {
			R<?> result = (R<?>) proceed;
			//如果data数据为null,则去获取返回data数据方法的返回值类型
			if(result.getData() == null) {
				MethodSignature signature = (MethodSignature) joinPoint.getSignature();
				Type type = signature.getMethod().getGenericReturnType();
				System.out.println(type);
				//com.plan.map.config.query.R<java.util.List<com.plan.map.module.test.entity.SgTmsNeB>>
				ParameterizedType parameterizedType = (ParameterizedType) type;
				Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
				for (Type t : actualTypeArguments) {
					System.out.println(t.getTypeName());
					//java.util.List<com.plan.map.module.test.entity.SgTmsNeB>
					if(t.getTypeName().contains("java.util.List")) {
						R<List<?>> reList = new R<>();
						BeanUtils.copyProperties(result, reList);
						reList.setData(new ArrayList<>());
						return reList;
					}
					//其他默认值处理
				}
			}
		}
		return proceed;
	}
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NVM-无缝切换Node版本

NVM-无缝切换Node版本 如果未使用nvm之前已经下载了node&#xff0c;并且配置了环境变量,那么此时删除这些配置(Node的环境以及Node软件),使用nvm是为了在某些项目中使用低版本的node NVM下载 进入github的nvm readme&#xff1a; https://github.com/coreybutler/nvm-windows…

企业如何利用数据打造新的人才战略?

由于利率上升、能源价格上涨、政治不确定性、全球供应限制以及寻找和资助昂贵人才的问题&#xff0c;企业的优先事项&#xff0c;也就是人们的优先事项&#xff0c;正在以前所未有的速度发生转变。在数据的基础之上迅速做出正确决策&#xff0c;并灵活精准地向他人传达意义。 …

必要的项目管理软件因素

什么样的项目管理软件好&#xff1f;对于一个项目团队来说&#xff0c;从项目开始到项目结束&#xff0c;需要多个部门的配合。每个成员可能会参与一个以上的项目&#xff0c;这通常需要并行的多个项目。据介绍&#xff0c;国外90%以上的项目是用软件管理的&#xff0c;而中国只…

dump 定位分析

在缺少pdb的时候如何分析dump&#xff1f; windbgidaWindbg定位崩溃位置 通过windbg打开dump&#xff0c;并且分析dump !analyze -v 分析&#xff1a; 分析dump&#xff1a; !analyze -v错误原因&#xff1a;读取空指针错误线程&#xff1a;00001e04&#xff0c;可通过命令…

【Linux进阶篇】磁盘及分区

目录 &#x1f341;文件系统 &#x1f342;虚拟机添加磁盘 &#x1f342;磁盘分区方式 &#x1f342;分区格式化 &#x1f342;挂载磁盘 &#x1f341;磁盘高级管理 &#x1f342;LVM&#xff1a;逻辑卷 &#x1f343;创建PV 物理卷 &#x1f343;创建VG 卷组 &#x1f343;创建…

说说你对 Webpack 的理解?解决了什么问题?

1、对webpack的理解 简单来说&#xff0c;webpack 是一个用于现代JavaScript应用程序的静态模块打包工具。 当 webpack处理应用程序时&#xff0c;它会在内部构建一个依赖图&#xff0c;此依赖图对应映射到项目所需的每个模块&#xff08;不再局限js文件&#xff09;&#xf…

【数据结构与算法】 - 双向链表 - 详细实现思路及代码

目录 一、概述 二、双向链表 三、双向链表实现步骤  &#x1f4cc;3.1 C语言定义双向链表结点  &#x1f4cc;3.2 双向链表初始化  &#x1f4cc;3.3 双向链表插入数据  &#x1f4cc;3.4 双向链表删除数据  &#x1f4cc;3.5 双向链表查找数据  &#x1f4cc;3.6 双向链…

总线仿真与测试工具CANoe介绍(图文并茂)

1、什么是CANoe CANoe是德国Vector公司的一款用于开发、测试和分析单个ECU和整个ECU网络的综合性工具,包括软件和硬件。它在整个开发过程中为网络设计者、开发和测试工程师提供支持:从规划到系统级测试。由于其多种变体和功能能够对不同的项目提供支持,被全球OEM和供应商广…

USB 速度识别

文章目录USB 速度识别低速设备全速设备高速设备空闲状态低速 / 全速设备高速设备SE0SE1USB 速度识别 在 USB host 或者 hub 的每个下行端口&#xff08;downstream facing port&#xff09;的 D 和 D- 上分别接了一个 15KΩ 的下拉电阻到地&#xff0c;这样&#xff0c;当端口…

SpringBoot自动装配原理(附面试快速答法)

文章目录SpringBoot自动装配原理1. 从调用SpringApplication构造器方法开始2. 解析启动类4.按需装配4.1 分析dubbo自动装配5. 如果定义自己的starter6. 面试答法SpringBoot自动装配原理 之前面试被问到这个题目&#xff0c;只会答一些spi、AutoConfigration注解、Import之类的&…

《SQLi-Labs》01. Less 1~5

Less-1 ~ Less-5 前言Less-1知识点题解 Less-2题解 Less-3题解 Less-4题解 Less-5知识点题解 sqli。开启新坑。 前言 对于新手&#xff0c;为了更加直观的看到 sql 注入语句&#xff0c;可以在以下文件添加两句&#xff1a; echo $sql; # 将构造的 sql 语句进行输出 echo &qu…

Maven依赖冲突分析和解决

使用maven管理jar包依赖时&#xff0c;可能会出现jar包版本冲突&#xff0c;不同版本的api调用方式可能不同&#xff0c;会出现NoSuchMethodError和ClassNotFoundException问题&#xff0c;甚至编译不通过&#xff0c;如&#xff1a;在common-lang3 的3.8.1版本中MethodUtils::…

Python中的@cache巧妙用法

缓存是一种空间换时间的策略&#xff0c;缓存的设置可以提高计算机系统的性能&#xff0c;这篇文章主要介绍了Python中的cache巧妙用法,需要的朋友可以参考下 Python中的cache有什么妙用&#xff1f; 缓存是一种空间换时间的策略&#xff0c;缓存的设置可以提高计算机系统的性…

Spark 简介与原理

目录标题1 Spark 简介与原理1.1 Spark与Hadoop的区别1.2 Spark的应用场景1.3 Spark的作业运行流程1.4 Spark 2.X与Spark 1.X的区别1 Spark 简介与原理 Spark 是一个大规模数据处理的统一分析引擎。 具有迅速、通用、易用、支持多种资源管理器的特点。 Spark生态系统: Spark SQL…

双榜加冕!加速科技荣登2023准独角兽中国未来独角兽双榜单

4月10日至11日&#xff0c;由杭州市人民政府、民建浙江省委会、中国投资发展促进会主办的第7届万物生长大会在杭州国际博览中心隆重举行。会上&#xff0c;中国投资发展促进会创投专委会、杭州市创业投资协会联合微链共同发布2023杭州市独角兽&#xff08;准独角兽&#xff09;…

【高危】vm2 <3.9.16 沙箱逃逸漏洞(CVE-2023-29199)

漏洞描述 vm2 是一个基于 Node.js 的沙箱环境&#xff0c;可以使用列入白名单的 Node 内置模块运行不受信任的代码。 vm2 3.9.16之前版本中&#xff0c;由于transformer.js中transformer函数中异常处理逻辑不够完善&#xff0c;攻击者可通过制造异常绕过handleException()并造…

精通 TensorFlow 2.x 计算机视觉:第一部分

原文&#xff1a;Mastering Computer Vision with TensorFlow 2.x 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 深度学习 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 不要担心自己的形象&#xff0c;…

Downie 4 4.6.14 MAC上最新最好用的一款视频下载工具

Downie for Mac 简介 Downie是Mac下一个简单的下载管理器&#xff0c;可以让您快速将不同的视频网站上的视频下载并保存到电脑磁盘里然后使用您的默认媒体播放器观看它们。 Downie 4 下载 Downie 4 for Mac Downie 4 for Mac软件特点 支持许多站点 -当前支持1000多个不同的…

printf里的格式控制符

%p&#xff1a;打印地址(指针地址)&#xff0c;十六进制形式输出&#xff0c;有多少位输出多少位&#xff0c;取决于是32 or 64位系统&#xff0c;如果输出不够位宽&#xff0c;左边补0。 &#xff05;f用来输出实数&#xff0c;以小数形式输出&#xff0c;默认情况下保留小数点…

自动化测试工程师需要具备什么技能?

如果是初入门的学习者&#xff0c;不建议拿一本书从头学&#xff0c;很可能会被里边一些专业术语和不常用的技术带偏&#xff0c;不论在公司还是在其他岗位上自学测试&#xff0c;都可以用自己搭建好的项目来练手&#xff08;如果在公司有现成的项目更好&#xff09;&#xff0…