SpringBoot 自定义 HandlerMethodArgumentResolver 搞定xml泛型参数解析

文章目录

  • 介绍
  • 一、解析简单 xml 数据案例
      • 引入 Jackson 的 xml 支持
      • 定义 Message 对象&MessageHeader 对象
      • 定义 Controller 方法
      • 调用结果
  • 二、解析带泛型的 XML 数据案例
      • 2.1 直接给 Message 加上泛型 T
      • 2.2 无法直接解析泛型参数了
  • 三、自定义 MVC 的参数解析器实现泛型参数解析
      • MVC 的消息解析器和方法参数解析器介绍
      • 让 MVC 解析泛型参数的方案
      • 具体方案
        • 定义一个类似@RequestBody的注解,来关联我们自定义的HandlerMethodArgumentResolver
        • 自定义HandlerMethodArgumentResolver 来实现将我们自定义注解中的泛型类告诉 JackSon-xml
        • 注入我们自己写的方法参数解析器
      • 测试效果
        • 将@RequestBody 改成我们自定义的注解@XmlGenerics
        • 结果成功解析
  • 四、你可能存在的一些疑问
      • 为什么响应的时候不需要加泛型 Jackson 都可以正确的进行序列化

介绍

在 SpringBoot 应用中,自带的 json 序列化框架是 fastxml-jackson。引入 jackson 的jackson-dataformat-xml包后,配合Bean上的jackson XML注解就可以自动的将 XML 请求参数进行反序列化,将返回对象进行序列化。

但是对于包含泛型的对象参数,由于泛型的擦除机制程序运行时无法得知泛型内容自然也就无法正确的将 XML 请求参数反序列化成指定的 Java 对象了。这时候想要正确解析,就得手动告诉 jackson 我想反序列化的对象是什么类型。本篇博客通过手写一个自定义的HandlerMethodArgument 来实现泛型对象的请求参数解析。

程序源代码下载 demoxml.rar


一、解析简单 xml 数据案例

目标,使用 Controller 将传入的 Message XML 格式数据解析成 Java 对象。
请求的 XML 格式

<Request><MessageHeader><Sender>EMSS</Sender><Receiver>ESB</Receiver><SendTime>20240401145010</SendTime><EventType>EMSS_SDZX</EventType><MsgId>8952f937-2f36-41b4-af71-2742fde43f09</MsgId></MessageHeader>
</Request>

引入 Jackson 的 xml 支持

<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>

定义 Message 对象&MessageHeader 对象

使用 JacksonXMLxxxx 的注解对对象进行标记。

@Data
@AllArgsConstructor
@NoArgsConstructor
@JacksonXmlRootElement(localName="Request")
public class Message {@JacksonXmlProperty(localName = "MessageHeader")private MessageHeader messageHeader;}
@Data
public class MessageHeader {/***必填 发送应用程序 例如HIS*/@JacksonXmlProperty(localName = "Sender")private String sender;/***必填 接收应用程序*/@JacksonXmlProperty(localName = "Receiver")private String receiver;/***必填 消息创建时间 格式:YYYY-MM-DD HH:MM:SS*/@JacksonXmlProperty(localName = "SendTime")private String sendTime;/*** 必填 事件类型 例如 ACK_PCA_SEARCH_ARCH*/@JacksonXmlProperty(localName = "EventType")private String eventType;/***必填 消息ID 建议用UUID生成*/@JacksonXmlProperty(localName = "MsgId")private String msgId;}

定义 Controller 方法

@Data
@AllArgsConstructor
@NoArgsConstructor
@JacksonXmlRootElement(localName="Request")
public class Message<T extends MessageBody> {@JacksonXmlProperty(localName = "MessageHeader")private MessageHeader messageHeader;@JacksonXmlProperty(localName = "MessageBody")private T messageBody;}

调用结果

可以看到 XML 参数可以被正确序列化和反序列化
image.png



二、解析带泛型的 XML 数据案例

2.1 直接给 Message 加上泛型 T

@Data
@AllArgsConstructor
@NoArgsConstructor
@JacksonXmlRootElement(localName="Request")
public class Message<T extends MessageBody> {@JacksonXmlProperty(localName = "MessageHeader")private MessageHeader messageHeader;@JacksonXmlProperty(localName = "MessageBody")private T messageBody;}
@Data
public class MessageBody implements Serializable {}

定义 MessageBody 的一个实现类 AnkeUpdatePatientLocaBody

/*** @author Jean* @date 2024/04/16*/
@Data
public class AnkeUpdatePatientLocaBody extends MessageBody {/***ankeid*/@JacksonXmlProperty(localName = "AnkeId")private String ankeId;/***经度,不能为空*/@JacksonXmlProperty(localName = "Longitude")private String longitude;/***纬度,不能为空*/@JacksonXmlProperty(localName = "Latitude")private String latitude;}

2.2 无法直接解析泛型参数了

1714402604488.png
可以看到 Controller 方法的 Message 泛型参数没有被正确的解析出来。为什么呢,因为泛型参数只存在于编译期,运行时程序并没有保存泛型数据。所有的泛型参数都会被擦除被替换为其上限类型(没有则替换为 Object)类型。不过虽然运行时没有泛型参数,但是类和方法上的泛型声明仍然可以通过反射获取到。



三、自定义 MVC 的参数解析器实现泛型参数解析

MVC 的消息解析器和方法参数解析器介绍

  1. 消息转换器(HttpMessageConverter):
    • 当请求到达并且需要读取请求体内容时,如使用@RequestBody注解的参数,Spring MVC会根据consumes指定的媒体类型,选择合适的HttpMessageConverter来将请求体中的数据(如JSON、XML)转换为Java对象。
    • 在响应阶段,如果Controller方法返回一个对象,并且需要转换为HTTP响应体(比如使用@ResponseBody注解),Spring会使用相应的HttpMessageConverter将Java对象转换为指定格式(如JSON、XML)的数据写入响应体。
  2. 消息解析器(HandlerMethodArgumentResolver):
  • 在调用Controller方法之前,Spring MVC会根据方法签名中的参数类型和注解,选择合适的HandlerMethodArgumentResolver来解析和填充参数。例如,对于@PathVariable@RequestParam@RequestHeader等注解的参数,Spring会使用对应的解析器来获取请求中的参数值。
  • 对于@RequestBody注解的参数,虽然实际的数据转换是由HttpMessageConverter完成,但触发这个转换的决策过程是在RequestBodyArgumentResolver(它是HandlerMethodArgumentResolver的一种)中进行的,它负责判断参数是否标记了@RequestBody,然后调用对应的HttpMessageConverter来处理请求体数据。

让 MVC 解析泛型参数的方案

  1. jackson 无法解析泛型 XML 参数的原因是因为泛型被擦除,jackson 也没有针对泛型做其他处理方案
  2. 我们写代码的时候是知道具体的类型的,我们通过某种防范将泛型类告诉 jackson 就行了
  3. jackson 负责实现 XMl 字符串解析成 Java 对象的过程,那我们自定义一个**HandlerMethodArgumentResolver **来实现泛型参数解析不就行了吗

具体方案

定义一个类似@RequestBody的注解,来关联我们自定义的HandlerMethodArgumentResolver
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface XmlGenerics {//用来保存泛型信息Class<? extends MessageBody> value();
}
自定义HandlerMethodArgumentResolver 来实现将我们自定义注解中的泛型类告诉 JackSon-xml

这里为了方便直接写了 Message.class,如果需要通用只需要改成根据 controller 方法来获取参数类型即可

/**** 自定义一个请求参数消息解析器,解析Message类型的Xml请求数据不走@RequestBody的解析器* 1.请求方法参数中使用了泛型后 jackson-xml 无法正确的反序列化,因为泛型抹除后无法获知具体的泛型类型。* 2.此处使用自定义注解保存泛型信息来进行反序列化参数。* <p>* {@link RequestResponseBodyMethodProcessor}  @RequestBody方法解析器* {@link org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration} jackson-xml的消息转换器的自动配置** @author* @date 2024/04/16 17:22*/
public class XmlMessageResolver implements HandlerMethodArgumentResolver {//用来解析xml的Mapperprivate final XmlMapper xmlMapper = new XmlMapper();public XmlMessageResolver() {//配置xmlMapper遇到未知属性时忽略,而不是报错xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);}/*** supports参数** @param parameter 参数* @return boolean*/@Overridepublic boolean supportsParameter(MethodParameter parameter) {boolean support = parameter.hasParameterAnnotation(XmlGenerics.class) && parameter.getParameterType().equals(Message.class);return support;}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {//读取请求体HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);String requestBody = StreamUtils.copyToString(servletRequest.getInputStream(), StandardCharsets.UTF_8);//获取注解参数中标识的泛型类型XmlGenerics parameterAnnotation = parameter.getParameterAnnotation(XmlGenerics.class);Class<? extends MessageBody> genericClass = parameterAnnotation.value();//构造类型参考对象TypeReference<Message<?>> typeRef = new TypeReference<Message<?>>() {@Overridepublic Type getType() {return new ParameterizedType() {//指定泛型实际参数,来自注解中的class标识@Overridepublic Type[] getActualTypeArguments() {return new Type[]{genericClass};}//获取参数类型@Overridepublic Type getRawType() {return Message.class;}//标识泛型参数所属的内部类类型,这里不包含内部类直接返回NULL@Overridepublic Type getOwnerType() {return null;}};}};//使用参考类型来反序列化XML请求数据Message<?> message = xmlMapper.readValue(requestBody, typeRef);return message;}}
注入我们自己写的方法参数解析器
@Configuration
public class WebConfig implements WebMvcConfigurer {// ... 上面的HttpMessageConverter配置...@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {argumentResolvers.add(0, new XmlMessageResolver());System.out.println("注入完成");}
}

测试效果

将@RequestBody 改成我们自定义的注解@XmlGenerics
@RestController
public class AnkeDataReceiverController {/*** 接收xml消息** @return {@link Message}*/@PostMapping(value = "/test", consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE)public Message pushPatientDataToJjld(@XmlGenerics(AnkeUpdatePatientLocaBody.class) Message<AnkeUpdatePatientLocaBody> message) {AnkeUpdatePatientLocaBody messageBody = message.getMessageBody();System.out.println(messageBody);return message;}
}
结果成功解析

image.png



四、你可能存在的一些疑问

为什么响应的时候不需要加泛型 Jackson 都可以正确的进行序列化

因为响应的时候,已经存在对象了,可以直接获取到对象及其参数的真实类型。所以反序列化不会存在这种问题

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全志ARM-蜂鸣器

sh操作准备&#xff1a; 1.使Tab键的缩进和批量对齐为4格 在/etc/vim/vimrc 中添加一项配置 set tabstop 4; 也可以再加一行 set nu显示代码的行数 vim的设置&#xff0c;修改/etc/vim/vimrc文件&#xff0c;需要用超级用户权限 /etc/vim/vimrc set shiftwidth4 设置批量…

助力企业部署国产云原生数据库 XSKY星辰天合与云猿生完成产品互兼容认证

近日&#xff0c;北京星辰天合科技股份有限公司&#xff08;简称&#xff1a;XSKY 星辰天合&#xff09;与杭州云猿生数据有限公司&#xff08;简称“云猿生”&#xff09;完成了产品互兼容认证&#xff0c;星辰天合企业级分布式统一数据平台 XEDP 与云猿生的开源数据库管控平台…

CJSON工具类

4.4.3.CJSON工具类 OpenResty提供了一个cjson的模块用来处理JSON的序列化和反序列化。 官方地址&#xff1a; https://github.com/openresty/lua-cjson/ 1&#xff09;引入cjson模块&#xff1a; local cjson require "cjson"2&#xff09;序列化&#xff1a; …

竞赛 基于机器视觉的二维码识别检测 - opencv 二维码 识别检测 机器视觉

文章目录 0 简介1 二维码检测2 算法实现流程3 特征提取4 特征分类5 后处理6 代码实现5 最后 0 简介 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于机器学习的二维码识别检测 - opencv 二维码 识别检测 机器视觉 该项目较为新颖&#xff0c;适合作为竞赛课…

网络层 --- IP协议

目录 1. 前置性认识 2. IP协议 3. IP协议头格式 3.1. 4位版本 3.2. 4位首部长度 3.3. 8位服务类型 3.4. 16位总长度 3.5. 8位生存时间 TTL 3.6. 8位协议 3.7. 16位首部检验和 3.8. 32位源IP和32位目的IP 4. 分片问题 4.1. 为什么要分片 4.2. 分片是什么 4.2.1. …

HTTP/1.1,HTTP/2.0和HTTP/3.0 各版本协议的详解(2024-04-24)

1、HTTP介绍 HTTP 协议有多个版本&#xff0c;目前广泛使用的是 HTTP/1.1 和 HTTP/2&#xff0c;以及正在逐步推广的 HTTP/3。 HTTP/1.1&#xff1a;支持持久连接&#xff0c;允许多个请求/响应通过同一个 TCP 连接传输&#xff0c;减少了建立和关闭连接的消耗。 HTTP/2&#…

win10 配置OpenCV LNK2019 无法解析的外部符号 “void __cdecl cv::imshow

1 遇到问题 严重性 代码 说明 项目 文件 行 禁止显示状态 详细信息 错误 LNK2019 无法解析的外部符号 “void __cdecl cv::imshow(class std::basic_string<char,struct std::char_traits,class std::allocator > const &,class cv::debug_build_guard::_InputArray…

MS17-010---利用“永恒之蓝”漏洞攻击 win7主机

免责声明:本文仅做技术交流与学习.... 目录 一.前置知识 1.何为永恒之蓝&#xff1f; 2.什么是SMB协议&#xff1f; 3.SMB工作原理是什么&#xff1f; 二、实验环境 三、实验步骤 nmap扫描 msf一把梭哈 shell执行命令 远程连接 一&#xff0e; 二&#xff0e; 一.前…

面向对象编程三大特征:封装、继承、多态

封装、继承、多态 1. 封装 1.1 介绍 封装(encapsulation)就是把抽象出的数据 [属性] 和对数据的操作 [方法] 封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作 [方法] ,才能对数据进行操作。 1.2 封装的理解和好处 1) 隐藏实现细节:方法(连接数据库)<…

用OpenCV先去除边框线,以提升OCR准确率

在OpenCV的魔力下&#xff0c;我们如魔法师般巧妙地抹去表格的边框线&#xff0c;让文字如诗如画地跃然纸上。 首先&#xff0c;我们挥动魔杖&#xff0c;将五彩斑斓的图像转化为单一的灰度世界&#xff0c;如同将一幅绚丽的油画化为水墨画&#xff0c;通过cv2.cvtColor()函数的…

通过AI助手实现一个nas定时任务更新阿里云域名解析

一.通过AI助手实现一个ip-domain.py的脚本 起一个Python脚本&#xff0c;ip-domain.py&#xff1b;注意已安装Python3.的运行环境&#xff1b;将下面阿里云相关配置添加&#xff0c;注意这里引用了两个包&#xff0c;requests和alibabacloud_alidns20150109&#xff1b;执行前…

如何利用有限的数据发表更多的SCI论文?——利用ArcGIS探究环境和生态因子对水体、土壤和大气污染物的影响

原文链接&#xff1a;如何利用有限的数据发表更多的SCI论文&#xff1f;——利用ArcGIS探究环境和生态因子对水体、土壤和大气污染物的影响https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247602528&idx6&snc89e862270fe54239aa4f796af07fb71&chksmfa82…

数据可视化在不同行业中有哪些应用?

数据可视化即通过图表的形式将数据的内在信息有逻辑性地呈现给用户&#xff0c;使用户更容易发现数据中蕴藏的规律&#xff0c;找出问题&#xff0c;进而做出决策&#xff1b;另一方面&#xff0c;数据可视化项目也是一张重要的名片&#xff0c;是企业数字化建设效果的呈现。本…

Spring基于AspectJ实现验签切点

文章目录 引言I AspectJ 依赖II 验签切点2.1 匹配方法执行的连接点2.2 设置带有CustomAnnotation注解的方法为切点III 案例:验签2.1 用法2.2 定义注解2.3 定义切面和切点引言 需求:验签 实现:基于AspectJ实现验签切点 I AspectJ 依赖 AspectJ 是一个基于 Java 语言的 AOP …

《HelloGitHub》第 97 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 Python、…

分布式存储 Ceph 的演进经验

从 2004 年到今天&#xff0c;Ceph 的存储后端一直都在演变&#xff0c;从最开始基于 B 树的 EBOFS 演变到今天的 BlueStore&#xff0c;存储后端已经变得非常成熟&#xff0c;新的存储系统不仅能够提供良好的性能&#xff0c;还有着优异的兼容性。我们在这篇文章中将要简单介绍…

服务器数据恢复—Storwize V3700存储数据恢复案例

服务器存储数据恢复环境&#xff1a; 某品牌Storwize V3700存储&#xff0c;10块硬盘组建了2组Mdisk加入到一个存储池中&#xff0c;一共创建了1个通用卷来存放数据&#xff0c;主要数据为oracle数据库。 服务器存储故障&#xff1a; 其中一组Mdisk中两块磁盘出现故障离线&…

使用selenium时出现element click intercepted报错的解决办法

win10&#xff0c;python3.8.10。 selenium版本如下&#xff08;用pip38 show selenium查看&#xff09;&#xff1a; 在定位中&#xff0c;定位了一个按钮&#xff08;特点&#xff1a;button下还有span然后才是文本&#xff09;&#xff0c;代码如下&#xff1a; from sele…

枚举(enum)/共用体(union)/结构体(struct)---详解

前言 C语言包含内置类型和自定义类型。 其实C语言中有内置类型&#xff0c;包含&#xff1a;char,short,int,long,long long,float,double,long double ,这些是C语言本身支持的现成的类型。 但仅仅只有内置类型是远远不够的&#xff0c;在描述一个复杂对象是无法使用内置类型来…

Linux工具篇 之 vim概念 操作 及基础指令讲解

学校不大 创造神话 讲桌两旁 陨落的王 临时抱佛脚 佛踹我一脚 书山有路勤为径 游戏玩的很起劲 想要计算机学的好&#xff0c;我的博客列表是个宝 –❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–❀–❀–❀–❀–❀–❀…