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的Mapper
    private final XmlMapper xmlMapper = new XmlMapper();

    public XmlMessageResolver() {
        //配置xmlMapper遇到未知属性时忽略,而不是报错
        xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * supports参数
     *
     * @param parameter 参数
     * @return boolean
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean support = parameter.hasParameterAnnotation(XmlGenerics.class) && parameter.getParameterType().equals(Message.class);
        return support;
    }

    @Override
    public 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<?>>() {
            @Override
            public Type getType() {
                return new ParameterizedType() {
                    //指定泛型实际参数,来自注解中的class标识
                    @Override
                    public Type[] getActualTypeArguments() {
                        return new Type[]{genericClass};
                    }

                    //获取参数类型
                    @Override
                    public Type getRawType() {
                        return Message.class;
                    }

                    //标识泛型参数所属的内部类类型,这里不包含内部类直接返回NULL
                    @Override
                    public Type getOwnerType() {
                        return null;
                    }
                };
            }
        };
        //使用参考类型来反序列化XML请求数据
        Message<?> message = xmlMapper.readValue(requestBody, typeRef);
        return message;
    }

}
注入我们自己写的方法参数解析器
@Configuration
public class WebConfig implements WebMvcConfigurer {

    // ... 上面的HttpMessageConverter配置...
    @Override
    public 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 都可以正确的进行序列化

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

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

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

相关文章

智能BI平台(后端)-- 项目介绍

文章目录 项目介绍需求分析基础架构图优化架构图技术栈 开个新坑&#xff0c;预计时间不会很长 项目介绍 BI商业智能&#xff1a;数据可视化&#xff0c;报表可视化系统 主流BI&#xff1a;帆软BI&#xff0c;小马BI&#xff0c;微软 Power BI 传统BI&#xff1a;[查看传统 BI…

精益生产咨询公司在企业转型中发挥的作用有哪些?

在全球化竞争日益激烈的今天&#xff0c;企业转型已成为许多组织求生存、谋发展的必经之路。而在这条道路上&#xff0c;精益生产咨询公司的作用愈发凸显&#xff0c;它们如同企业转型的得力助手&#xff0c;帮助企业在复杂的商业环境中找到新的增长点&#xff0c;实现更高效、…

I forgot my Plex Account PIN; how can I reset it? How can I change my PIN?

If you’ve set a PIN on your Plex account, it’s possible to reset or remove that PIN. Related Page: Plex Home Regular Plex Account If you know the current PIN If the current PIN is known, then simply edit the current PIN on the Settings > Users &…

Cisco NX-OS System Software - ACI 16.0(5h)

Cisco NX-OS System Software - ACI 16.0(5h) 适用于 ACI 模式下的 Cisco Nexus 9000 系列交换机 请访问原文链接&#xff1a;Cisco NX-OS System Software - ACI 16.0(5h)&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Cis…

从零开始的软件测试学习之旅(七)接口测试流程及原则案例

接口测试三要素及案例 接口测试介绍接口预定义接口测试的主要作用测试接口流程如下接口测试三要素接口测试分类RESTful架构风格RESTful架构三要素要素一要素二要素三 RESTful架构风格实现restful架构案例接口测试流程接口测试原则功能测试自动化测性能测试 复习复盘 接口测试介…

AI视频教程下载:学会用AI创作文本图片音频视频

在不断发展的科技领域&#xff0c;人工智能 (AI) 是毋庸置疑的冠军&#xff0c;它是一种不断创新的力量&#xff0c;在我们的生活中扮演着越来越重要的角色。随着 2023 年的到来&#xff0c;我们诚挚地欢迎您加入人工智能精通课程的大门。 这不仅仅是一个课程&#xff0c;它专为…

外网访问内网电脑?

随着互联网的发展&#xff0c;越来越多的人需要在不同地区间进行远程访问和组网。而在复杂网络环境下&#xff0c;外网访问内网电脑常常成为一个令人头痛的问题。现在有一款名为【天联】的组网产品却解决了这个难题。 【天联】组网是由北京金万维科技有限公司自主研发的一款内网…

【管理篇】管理三步曲:任务执行(三)

目录标题 多任务并行如何应对?如何确保项目有效的执行项目执行过程中常见的问题1、目标不明确2、责任不明确3、流程不健全4、沟通不到位 如何有效执行任务 如何让流程机制有效的执行 研究任务管理&#xff0c;就是为了把事情做出来&#xff0c;产出实实在在的业绩和成果&#…

京东物流:表格技术在物流行业的敏捷应用实践

“物流大促期间&#xff0c;在出库单量积压的场景下&#xff0c;不同仓的生产操作人员需要在统一数据源的基础上进行基于自身仓情况的个性化查询分析&#xff0c;从而能够实时监控客单情况&#xff0c;防止积压。要想实现这样的功能&#xff0c;对数据分析平台的要求就非常高。…

通过 Java 操作 redis -- 基本通用命令

目录 使用 String 类型的 get 和 set 方法 使用通用命令 exists &#xff0c;del 使用通用命令 keys 使用通用命令 expire,ttl 使用通用命令 type 要想通过 Java 操作 redis&#xff0c;首先要连接上 redis 服务器&#xff0c;推荐看通过 Java 操作 redis -- 连接 redis 关…

深入探索van Emde Boas树:原理、操作与C语言实现

van Emde Boas (vEB) 树是一种高效的数据结构&#xff0c;用于处理整数集合。它是由荷兰计算机科学家Jan van Emde Boas在1977年提出的。vEB树在处理整数集合的查找、插入、删除和迭代操作时&#xff0c;能够以接近最优的时间复杂度运行。vEB树特别适合于那些元素数量在某个较小…

CSS引用

CSS定义 层叠样式表&#xff1a;&#xff08;Cascading Style Sheets,缩写为css&#xff09;,是一种样式表语言&#xff0c;用来描述HTML文档的呈现&#xff08;美化内容&#xff09; 书写位置&#xff1a;title标签下方添加style双标签&#xff0c;style标签里写入CSS代码 在s…

Spring Security 入门1

1. 概述 基本上&#xff0c;在所有的开发的系统中&#xff0c;都必须做认证(authentication)和授权(authorization)&#xff0c;以保证系统的安全性。 authentication [ɔ,θɛntɪ’keʃən] 认证 authorization [,ɔθərɪ’zeʃən] 授权 以论坛举例子&#xff1a; 【认证…

Covalent引入五个新网络运营商,提升去中心化特性和数据安全性

为了进一步扩大运营商基础以并践行去中心化网络基础设施的宗旨&#xff0c;Covalent Network&#xff08;CQT&#xff09;在网络中引入了五个新的区块样本生产者&#xff08;BSPs&#xff09;角色。该举措不仅重申了 Covalent Network&#xff08;CQT&#xff09;对社区驱动协议…

Dynamics 365入门:轻松创建您的首个应用

大家好&#xff0c;我是嘻嘻一个从事软件开发的老兵&#xff0c;需要交流可以加VX:lilinsans_weixin, 今天接上篇&#xff1a; 注册 Dynamics 365后&#xff0c;如果创建自己的第一个应用 注册完试用版可以以试用30天。今天我就分享一下如何创建第一个应用 1、Dynamics 36…

##08 数据加载与预处理:PyTorch的心脏

文章目录 前言深入理解torch.utils.data数据集(Dataset)数据加载器(DataLoader) 实战演练&#xff1a;创建自定义数据集数据转换(Transform)数据加载总结 前言 在深度学习的宇宙中&#xff0c;数据是燃料&#xff0c;模型是发动机。而在PyTorch的世界中&#xff0c;torch.util…

制作微信小程序的常见问题,2024新手小白入门必看

在当今高度数字化的世界&#xff0c;移动应用已经在日常生活和工作中不可或缺。在众多的应用程序中&#xff0c;有一个平台在中国市场上脱颖而出&#xff0c;占有绝对的一席之地——微信。 虽然被称为世界上最流行的消息和社交媒体平台之一&#xff0c;但微信提供了一个让其能…

计算机网络5——运输层1概述与UDP

文章目录 一、协议概述1、进程之间通信2、运输层的两个主要协议3、运输层的端口1&#xff09;服务器端使用的端口号2&#xff09;客户端使用的端口号 二、用户数据报协议 UDP1、UDP 概述2、案例分析3、UDP的首部格式 一、协议概述 1、进程之间通信 从通信和信息处理的角度看&…

邮件群发还能用吗

邮件群发仍然可以使用。不过&#xff0c;在进行邮件群发时&#xff0c;可能会遇到一些问题&#xff0c;如选择合适的邮件群发软件、应对垃圾邮件过滤器的挑战、管理收件人列表、邮件内容的个性化和定制、邮件投递的时间管理以及避免被列入黑名单等。 为了优化邮件群发的效果&a…

微信小程序知识点归纳(一)

前言&#xff1a;适用于有一定基础的前端开发同学&#xff0c;完成从网页开发到小程序开发的知识转换。 先立框架&#xff0c;后砌墙壁 回顾&#xff1a;了解微信小程序开发流程-CSDN博客 初始页面结构&#xff0c;三部分pages、utils、配置&#xff0c;分别存放页面、工具类…