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.mzph.cn/bicheng/8233.shtml

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

相关文章

OCR文本识别模型CRNN

CRNN网络结构 论文地址&#xff1a;https://arxiv.org/pdf/1507.05717 参考&#xff1a;https://blog.csdn.net/xiaosongshine/article/details/112198145 git:https://github.com/shuyeah2356/crnn.pytorch CRNN文本识别实现端到端的不定长文本识别。 CRNN网络把包含三部分&…

两个手机在一起ip地址一样吗?两个手机是不是两个ip地址

在数字时代的浩瀚海洋中&#xff0c;手机已经成为我们生活中不可或缺的一部分。随着移动互联网的飞速发展&#xff0c;IP地址成为了连接手机与互联网的桥梁。那么&#xff0c;两个手机在一起IP地址一样吗&#xff1f;两个手机是不是两个IP地址&#xff1f;本文将带您一探究竟&a…

微火全域外卖系统是什么?为什么市场占有率这么高?

近日&#xff0c;全域外卖领域又出现了新变动&#xff0c;一个名为微火的品牌凭借着其全域外卖系统&#xff0c;在短短几个月的时间里&#xff0c;就占领了大部分市场。截止发稿日期前&#xff0c;微火全域外卖系统的市场占有率已经超过48%。 据了解&#xff0c;所谓的全域外卖…

微信小程序之搜索框样式(带源码)

一、效果图&#xff1a; 点击搜索框&#xff0c;“请输入搜索内容消失”&#xff0c;可输入关键字 二、代码&#xff1a; 2.1、WXML代码&#xff1a; <!--搜索框部分--><view class"search"><view class"search-btn">&#x1f50d;&l…

Fatal error: invalid -march= option: `armv7-a‘

我也是不知所措。 解决办法 参考&#xff1a; 嵌入式交叉编译&#xff1a;libvpx&#xff08;全网首发&#xff09;-CSDN博客

数据库复习2

试述SQL的特点 有两个关系 S(A,B,C, D)和 T(C,D,E,F)&#xff0c;写出与下列查询等价的 SQL 表达式: 用SQL语句建立第2章习题6中的4个表&#xff1b;针对建立的4个表用SQL完成第2章习题6中的查询 针对习题4中的4个表试用SQL完成以下各项操作 (1)找出所有供应商的姓名和所在城市…

[图解]SysML和EA建模住宅安全系统-02

1 00:00:00,900 --> 00:00:02,690 这个就是一个块定义图了 2 00:00:03,790 --> 00:00:04,780 简称BDD 3 00:00:05,610 --> 00:00:08,070 实际上就是UML里面的类图 4 00:00:08,080 --> 00:00:09,950 和组件图的一个结合体 5 00:00:13,150 --> 00:00:14,690 我…

WDW-10B微机控制电子万能试验机技术方案

一&#xff0e;设备外观照片&#xff1a; 项目简介&#xff1a; 微机控制电子式万能试验机是专门针对高等院校、各种金属、非金属科研厂家及国家级质检单位而设计的高端微机控制电子式万能试验机、计算机系统通过全数字控制器&#xff0c;经调速系统控制伺服电机转动&#xff…

【FFmpeg】调用ffmpeg进行H264软解

调用FFmpeg库实现264软件解码 1. FFmpeg的编译2. 调用FFmpeg实现H264软解2.1 基本框架2.2 代码实现2.3 测试结果 3. 分析工具3.1 码流分析3.2 YUV分析 调用FFmpeg库实现264软件编码可参考http://t.csdnimg.cn/FfBOY 1. FFmpeg的编译 FFmpeg在Windows下的编译参考&#xff1a;h…

MT3033 新的表达式

代码&#xff1a; #include <bits/stdc.h> using namespace std; bool is_op(char c) {return c & || c |; } int priority(char op) { // 运算优先级。如果有-*/等别的运算符&#xff0c;则这个函数很有必要if (op & || op |){return 1;}return -1; } voi…

.gitignore 文件的说明文档

今天有同事问我这个问题&#xff0c;我寻思着网上有很多资料可以查看了&#xff0c;写的也都非常好。 她说就得看我写的&#xff0c;做一个简单记录&#xff1a; .gitignore 文件用于在 Git 版本控制系统中指定不需要跟踪的文件和目录 1、确定要忽略的文件和目录&#xff1a;…

数据链路层(详细版)【01】

数据链路层是在物理层和网络层之间的协议&#xff0c;提供相邻节点的可靠数据传输 一、从体系结构来看数据链路层 数据链路层是为上下两层提供服务或者上下两层向他传送数据&#xff08;服务【垂直】&#xff09;&#xff1b;与其对等层之间用帧进行通信&#xff08;协议【水平…

2024年51cto下载的视频怎么导出

如果你喜欢在51cto上观看各种专业技术视频&#xff0c;那么你可能想将喜欢的视频保存到本地设备中&#xff0c;以便随时随地观看。今天&#xff0c;我们就来探讨一下如何在2024年将51cto下载的视频导出到你的设备中 下载51cto的工具我已经打包好了&#xff0c;有需要的自己下载…

重学java 31.API 2.StringBuilder

总有一天&#xff0c;我不再畏惧任何人的离开 —— 24.5.8 StringBuilder的介绍 1.概述 一个可变的字符序列,此类提供了一个与StringBuffer兼容的一套API&#xff0c;但是不保证同步&#xff08;线程不安全&#xff0c;效率高&#xff…

Qt 6.7 正式发布!

本文翻译自&#xff1a;Qt 6.7 Released! 原文作者&#xff1a;Qt Group研发总监Volker Hilsheimer 在最新发布的Qt 6.7版本中&#xff0c;我们大大小小作出了许多改善&#xff0c;以便您在构建现代应用程序和用户体验时能够享受更多乐趣。 部分新增功能已推出了技术预览版&a…

scikit-learn多因子线性回归预测房价

1.首先是单因子线性回归预测房价 import numpy as np import pandas as pd from matplotlib import pyplot as plt from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score# 1.读取csa房屋数据 path D:/pythonDATA/us…

地道俄语口语,柯桥俄语培训哪家好

1、по-моему 依我看&#xff1b;在我看来 例&#xff1a; По-моему, сегодня будет дождь. 依我看, 今天要下雨。 Сделай по-моему. 按我的办法干吧 2、кажется 似乎是&#xff1b;看起来 例&#xff1a; Парень, …

mvc区域、Html.RenderAction、Html.RenderPartial、 模板、section

根据上图 Html.RenderPartial 与 Html.RenderAction 区别 RenderAction 会把对应的视图结果渲染 RenderPartial 会把html视图直接渲染 模板

mysql binlog 如何区分db

binlog不是InnoDB存储引擎特有的日志文件&#xff0c;是属于mysql server自己的日志文件。 提交事务的时候&#xff0c;同时会写入binlog 在MySQL中&#xff0c;Binary Log&#xff08;binlog&#xff09;记录了数据库更改操作的所有细节&#xff0c;对于实现数据复制、恢复以…

java 语言写一个装饰器模式代码

装饰器模式&#xff08;Decorator Pattern&#xff09;允许你动态地给一个对象添加一些额外的职责。就增加功能来说&#xff0c;装饰器模式相比生成子类更为灵活。下面是一个简单的 Java 装饰器模式的示例代码&#xff1a; 首先&#xff0c;我们定义一个接口 Component&#x…