XML数据格式的接口交互(Java)(Springboot+Mybatis

您所在的位置:网站首页 java调用其他接口 XML数据格式的接口交互(Java)(Springboot+Mybatis

XML数据格式的接口交互(Java)(Springboot+Mybatis

2024-02-01 03:48| 来源: 网络整理| 查看: 265

文章目录 前言一、需求描述二、操作步骤1.技术选型2.控制器和实体类代码3.JAXB的常用注解4.实体类 总结

前言

最近Json格式数据在互联网行业非常火爆,但是也存在不少XML格式的数据交互

一、需求描述

访问接口时,入参关键值为String类型的input,input中参数以XML数据结构传入。最后调用统一的接口,传入要访问的方法名称,动态调用类方法(反射机制) input示例:15 即标签中是真正需要的参数,而在后端需要解析这段XML字符串并提取出有用的元素。

最后返回的XML格式为:

成功:

1 成功 文档数据实例格式

失败:

-1 失败的原因

成功的详细示例:

1 成功 1 100 1 2 10001 20131 张三 13888888888 2021-01-19T19:47:27 10001,10002 10002 20180 李四 13512338888 2021-01-19T19:47:27 10001,10002

由于踩的坑比较多,所以想把它记录下来,作为学习路上的一点记录。 (PS:作者是一个小白,刚学了一个月的Java,所以有说错或者没说明白的地方欢迎各位大佬点评指正)

二、操作步骤 1.技术选型

XML部分的数据处理采用JAXB和hutool工具类,持久层选用Mybatis-Plus,项目整体采用Springboot框架。hutool依赖如下(其它依赖建议自己安装,这里只提供了工具类的依赖):

cn.hutool hutool-all 5.5.2 2.控制器和实体类代码

先将完整的接口控制器部分的代码贴出来:

@GetMapping("/getuserlist") @ApiOperation(value = "分页获取用户列表信息") public String getuserlist(@RequestParam(value = "input") String input) { try { Document document = XmlUtil.parseXml(input); Node firstChild = document.getFirstChild(); DicEmployeeListInput dicEmployeeListInput = XmlUtil.xmlToBean(firstChild, DicEmployeeListInput.class); IPage userList = dicEmployeeBaseService.getUserList(dicEmployeeListInput); return success(userList); } catch (Exception e) { return failure(e.getMessage()); } }

接下来分析问题,前端传入XML格式的字符串,那么要取得字符串中的节点值,我们需要将它转换为文件或对象,这边我选择用hutool工具类将其转换为对象

Document document = XmlUtil.parseXml(input);

这样我们得到了一个Document对象,根据hutool文档,可知要得到其节点可用方法:

Node firstChild = document.getFirstChild();

随后我们可以调用hutool的xmlToBean方法,把节点对象直接赋值给实体类

DicEmployeeListInput dicEmployeeListInput = XmlUtil.xmlToBean(firstChild, DicEmployeeListInput.class);

这边xmlToBean方法接收两个参数,第一个参数是Node类型的xml格式数据,第二个参数是你自己创建的实体类的字节码文件

下一步是通过SQL语句得到需要的列表对象,我这边用的Mybatis-Plus,得到一个IPage对象,这边更详细的代码主要是做逻辑处理和SQL的,没有涉及到XML这一块,就不再赘述了,每个人的数据操作不一样,没有参考的必要。

接下来进入最后一句代码return success(userList);

这里我有写一个基类BaseController2,调用的是里面的success()方法。

下面贴出BaseController2部分的代码:

/** * @Author: Wangyx * @Date: 2021/3/2 17:25 * @Description: */ public abstract class BaseController2 { public String success(IPage page) { /*if (page.getRecords().size() != 0) {*/ OpenItemsDto openItemsDto = new OpenItemsDto(); openItemsDto.setItem(page.getRecords()); OpenResponseDto openResponseDto = new OpenResponseDto(); openResponseDto.setCurrentPage(page.getCurrent()); openResponseDto.setPageCount(page.getPages()); openResponseDto.setPerPage(page.getSize()); openResponseDto.setTotalCount(page.getTotal()); openResponseDto.setItems(openItemsDto); OpenBodyDto openBodyDto = new OpenBodyDto(); openBodyDto.setResponse(openResponseDto); return success(openBodyDto); /*} else { return success(new OpenBodyDto()); }*/ } public String success(OpenBodyDto openBodyDto) { String success = XMLUtil.convertToXml(new OpenMessageDto(openBodyDto, new OpenHeadDto(1L, "成功"))); success = success.replaceAll("",""); success = success.replaceAll("",""); return success; } public String success(List obj) { OpenItemsDto openItemsDto = new OpenItemsDto(); openItemsDto.setItem(obj); OpenResponseNopageDto openResponseNopageDto = new OpenResponseNopageDto(); openResponseNopageDto.setItems(openItemsDto); OpenBodyDto openBodyDto = new OpenBodyDto(); openBodyDto.setResponse(openResponseNopageDto); return success(openBodyDto); } public String failure(String msg) { String failure = XMLUtil.convertToXml(new OpenMessageDto(new OpenBodyDto(), new OpenHeadDto(-1L, msg))); failure = failure.replaceAll("",""); return failure; } }

BaseController2只是方便返回数据的一个类,重要的是怎么得到数据,里面的实体类是关键。

接下来聊一聊几个实体类吧,看了开头那些复杂的结构,接下来就一步一步的剖析它们。

首先看到一个大块的,其底下分为了两个子标签和,现在把message看作一个实体类,那么head和body就是它的成员属性。

即新建实体类OpenMessageDto:

/** * @Author: Wangyx * @Date: 2021/3/4 14:45 * @Description: */ @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "message") @XmlType(propOrder = { "head", "body" }) public class OpenMessageDto implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty("respone集合") @JsonProperty("body") @XmlElement(name = "body") private OpenBodyDto body; @ApiModelProperty("head集合") @JsonProperty("head") private OpenHeadDto head; public OpenBodyDto getBody() { return body; } public void setBody(OpenBodyDto body) { this.body = body; } public OpenHeadDto getHead() { return head; } public void setHead(OpenHeadDto head) { this.head = head; } public OpenMessageDto(OpenBodyDto body, OpenHeadDto head) { this.body = body; this.head = head; } public OpenMessageDto() { } }

重点来了,以上有关XML的注解有什么用,一个个说。

3.JAXB的常用注解

@XmlRootElement 作用和用法: **类级别的注解,**将类映射为xml全局元素,也就是根元素。就像spring配置文件中的beans。 属性: 该注解含有name和namespace两个属性。namespace属性用于指定生成的元素所属的命名空间。name属性用于指定生成元素的名字,若不指定则默认使用类名小写作为元素名。

@XmlElement 作用和用法: **字段,方法,参数级别的注解。**该注解可以将被注解的字段(非静态),或者被注解的get/set方法对应的字段映射为本地元素,也就是子元素。默认使用字段名或get/set方法去掉前缀剩下部分小写作为元素名(在字段名和get/set方法符合命名规范的情况下)。 属性: 该注解的属性常用的属性有:name、nillable、required、namespace、defaultValue

name属性可以指定生成元素的名字nillable属性可以指定元素的文本值是否可以为空,默认为false。当这个属性为true时,可以解决xsi:type="echoBody" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"这样的问题。 @XmlAccessorType 作用和用法: 包和类级别的注解。javaEE的API对该注解的解释是:控制字段是否被默认序列化。通俗来讲,就是决定哪些字段或哪些get/set方法对应的字段会被映射为xml元素,需要注意的是字段或get/set方法的访问权限(public/private)会影响字段是否被映射为xml元素 属性: 该注解只有一个value属性,可取的值是一个名为XmlAccessType的枚举类型里的值 XmlAccessType.PROPERTY: jaxb绑定类中的每个getter/setter对都将自动绑定到XML,除非用@XmlTransient注释。 只有在某些JAXB注释显式地注释字段时,字段才被绑定到XML。

当使用了该值,只要字段有对应的get/set方法对(**注意是成对出现,只有其中一个不会发生映射),**不需要使用@XmlElement注解,不论该方法的访问权限是什么(即使是private),jaxb就会将该字段映射成xml元素。不过最好加上@XmlElement注解,get/set方法任选一个即可,都加上会报错。

若在一个字段有set/get方法对但又在字段上添加@XmlElement注解会报属性重复的错误。

若没有set/get方法对,则需要在字段上使用@XmlElement注解才可以映射为xml元素,否则不会发生映射。

若get/set方法上使用了@XmlTransient注解,但想要对应字段发生映射,需要在对应字段上添加@XmlElement注解,此时不会报错,并将该字段映射为xml元素。

XmlAccessType.FIELD: jaxb绑定类中的每个非静态、非瞬态字段都将自动绑定到XML,除非使用@XmlTransient进行注释。 只有当某些JAXB注释显式地对getter/setter对进行注释时,它们才会绑定到XML。

每个非静态的字段(无论访问权限如何)都会被jaxb映射为xml元素,即使没有get/set方法对,即使没有使用@XmlElement元素,但最好加上该注解以表明该字段要被映射为xml元素。

虽然没有get/set方法对,也会发生映射,但加上get/set方法对也不会报错,因为我们经常会使用这两个方法。但注意,不能再在这两个方法上使用@XmlElement方法,否则会报属性重复的错误。

若在字段上使用了@XmlTransient注解,但还想让该字段发生映射,需要在该字段对应的get/set方法上添加@XmlElement

XmlAccessType.PUBLIC_MEMBER (该值为默认值): 每个公共getter/setter对和每个公共字段都将自动绑定到XML,除非使用@XmlTransient注释。只有在适当的JAXB注释显式地注释了字段或getter/setter对之后,才会将它们绑定到XML。

每个访问权限为public的字段,或者每个访问权限为public的get/set方法对,都会将字段映射为xml元素,即使不使用@XmlElement,但最好加上。不可同时存在public字段和对应的get/set方法对,不然会报属性重复的错误。

若使用@XmlElement注解,需要注意只能在字段或get/set方法添加,两者任选其一,否则会报属性重复的错误。

若字段不为public,get/set方法为public并使用了@XmlTransient,需要在字段上添加@XmlElement才会发生映射。

若字段为public并使用了@XmlTransient,get/set方法对不为public,需要在get/set方法上使用@XmlElement才会映射。

XmlAccessType.NONE: 任何字段或属性都不会绑定到XML,除非使用某些JAXB注释对它们进行特别注释。

任何字段,get/set方法对都不会发生映射,除非使用某些注解,如@XmlElement,@XmlElementWrapper等。

@XmlAccessorOrder

作用和用法:

包和类级别的注解。控制生成元素的顺序。

属性:

只有一个value属性,可取的值是一个名为XmlAccessOrder的枚举类型的两个值,XmlAccessOrder.ALPHABETICAL 和 XmlAccessOrder.UNDEFINED。默认为XmlAccessOrder.UNDEFINED,代表按照类中字段的顺序生成元素的顺序。

另一个值则代表按照字母表的顺序对生成的元素排序。但奇怪的是,只有jaxb按照field生成元素时,默认值才会生效,否则总是按照字母表的顺序排序。

@XmlElementWrapper

作用和用法:

字段和方法级别的注解。围绕被映射的xml元素生成包装元素。主要用在集合对象映射后生成包装映射结果的xml元素。

属性:

该注解有name、nillable、namespace、required四个属性,用法同上,不再赘述。

本例中的items和item标签即可通过此注解实现相同的效果。

示例:

@XmlElementWrapper(name = "items") @XmlElement(name = "item") private List item;

以上注解即可实现:

本例中由于仅作逻辑参考,代码的优化和结构化尚未来得及修改,所以主要看逻辑实现,后续优化可以慢慢看。

@XmlType

作用和用法:

类级别的注解。该注解有些复杂,主要使用的是它的propOrder属性,指定生成元素的顺序,更多属性功能尚未测试。

@XmlSeeAlso

作用和用法: 类级别的注解。该注解是为了解决泛型不确定的情况而生的,接收一个字节码文件数组,这些字节码文件便是有可能用到的泛型类型。

比如我现在定义了一个属性:

private List item;

假设这个泛型你想让它接收两个实体类,分别为User和Teacher,那么现在这个List的泛型并未指定,此时不加@XmlSeeAlso注解直接运行,会报一个错误,大致意思就是上下文类型未知之类的。

现在我们在类上加上这个注解:

@XmlSeeAlso({User.class,Teacher.class}) public class OpenBodyDto implements Serializable { private static final long serialVersionUID = 1L; }

这样一来,程序就可以正常运行并被解析了,但是会有一个小问题。 对应的xml标签上会出现诸如xsi:type="echoBody" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance这样的属性值,那么之前的注解有讲到过nillable可以解决这个问题,接下来新引入一个注解同样能够解决这个问题。

@XmlAnyElement 作用和用法: 属性级别的注解。该注解的功能目前用到了是为了去除xsi:type="echoBody" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance,暂时未作他用。 @XmlAnyElement(lax = true) private List item;

值得注意的一点是,@XmlAnyElement(lax = true)和nillable只能用一个,两个都存在的情况下会报错。

4.实体类

注解讲的差不多了,继续回到实体类,OpenMessageDto已经讲完了,那么按照需求,message的子标签还有和

看懂了message,后面的实体类都是一样的,直接上代码

OpenHeadDto:

/** * @Author: Wangyx * @Date: 2021/3/4 14:44 * @Description: */ @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "head") @XmlType(propOrder = { "retcode", "retmessage" }) public class OpenHeadDto implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty("状态码") @JsonProperty("retcode") private Long retcode; @ApiModelProperty("返回信息") @JsonProperty("retmessage") private String retmessage; public Long getRetcode() { return retcode; } public void setRetcode(Long retcode) { this.retcode = retcode; } public String getRetmessage() { return retmessage; } public void setRetmessage(String retmessage) { this.retmessage = retmessage; } public OpenHeadDto(Long retcode, String retmessage) { this.retcode = retcode; this.retmessage = retmessage; } public OpenHeadDto() { } }

OpenBodyDto:

/** * @Author: Wangyx * @Date: 2021/3/4 14:42 * @Description: */ @XmlAccessorType(XmlAccessType.FIELD) //@XmlRootElement(name = "body") @XmlType(propOrder = { "response" }) @XmlSeeAlso({OpenResponseNopageDto.class,OpenResponseDto.class}) public class OpenBodyDto implements Serializable { private static final long serialVersionUID = 1L; @XmlAnyElement(lax = true) private Object response; public Object getResponse() { return response; } public void setResponse(Object response) { this.response = response; } }

这边插句话,在OpenBodyDto类中,response类型是Object类,是因为response分为了分页的和不分页的,所以有两个,不是确定的类型。

OpenResponseDto:

/** * @Author: Wangyx * @Date: 2021/3/4 14:38 * @Description: */ @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "response") @XmlType(propOrder = { "currentPage", "perPage", "pageCount", "totalCount", "items" }) public class OpenResponseDto implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty("当前页") @JsonProperty("current_page") private Long currentPage; @ApiModelProperty("每页大小") @JsonProperty("per_page") private Long perPage; @ApiModelProperty("总页数") @JsonProperty("page_count") private Long pageCount; @ApiModelProperty("总条数") @JsonProperty("total_count") private Long totalCount; @ApiModelProperty("记录集合") @JsonProperty("items") private OpenItemsDto items; public Long getCurrentPage() { return currentPage; } public void setCurrentPage(Long currentPage) { this.currentPage = currentPage; } public Long getPerPage() { return perPage; } public void setPerPage(Long perPage) { this.perPage = perPage; } public Long getPageCount() { return pageCount; } public void setPageCount(Long pageCount) { this.pageCount = pageCount; } public Long getTotalCount() { return totalCount; } public void setTotalCount(Long totalCount) { this.totalCount = totalCount; } public OpenItemsDto getItems() { return items; } public void setItems(OpenItemsDto items) { this.items = items; } }

OpenResponseNopageDto:

/** * @Author: Wangyx * @Date: 2021/3/4 16:51 * @Description: */ @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "response") public class OpenResponseNopageDto implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty("记录集合") @JsonProperty("items") private OpenItemsDto items; public OpenItemsDto getItems() { return items; } public void setItems(OpenItemsDto items) { this.items = items; } }

OpenItemsDto:

/** * @Author: Wangyx * @Date: 2021/3/4 14:30 * @Description: */ @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "items") @XmlSeeAlso({OpenUserListItemDto.class,OpenDeptListItemDto.class}) public class OpenItemsDto implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty("数据集合") @JsonProperty("item") @XmlAnyElement(lax = true) private List item; public List getItem() { return item; } public void setItem(List item) { this.item = item; } }

其实这边的这个items实体类有点没必要,完全可以使用@XmlElementWrapper注解来实现相同的功能,这个后续也会进行优化,现在暂时就这样了。

至此为止,所有可复用的实体类都已经在了,往下就是每个不同需求要输出的实体类了,都装在item集合中,记得在@XmlSeeAlso注解添加对应实体类的字节码文件

开始挖了个坑,提到了动态调用类方法,现在过来填上。

/** * @Author: Wangyx * @Date: 2021/3/8 9:00 * @Description: */ @Service public class SysOpenApiManager extends XMLBase { @Autowired private SysOpenApiManager sysOpenApiManager; public String api() { try { String invoke = (String) sysOpenApiManager.getClass().getMethod(method, new Class[]{String.class}).invoke(sysOpenApiManager, new Object[]{input}); return invoke; } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); t.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } public String getuserlist() {#code...} public String getdeptlist() {#code...} }

主要使用了反射技术实现了方法的动态调用,但是

@Autowired private SysOpenApiManager sysOpenApiManager;

解释一下为什么这个类是SysOpenApiManager呢,之前不是写在控制器的吗。因为这篇文章的时间点跨越了三天(问就是懒),写到这的时候已经把该优化的结构和规范都优化ok了,一开始写逻辑思路的时候还是可以写在控制器里,方便自己测试。

这段代码很奇怪,为什么在本类中要用这样的方法创建类对象。 因为当对象声明为组件的时候,它是交给spring容器去管理的,容器会帮你进行初始化;但是如果使用new方法来调用对象,会跳过spring容器生成的对象,这时就无法进行初始化,所以在调用的时候会出现对象为null,并且对象里面以注入方式引用的对象也为null。可以仔细看看用new的方法,编译器不会直接报空指针异常,加了这个catch (InvocationTargetException e)后,才会报空指针异常,这是因为你的类引用了一个方法,被引用的这个方法报了空指针异常,但是返回的消息是对象中的方法有错误,而没有更详细的错误信息,所以需要加上这个catch看看调用的方法中发生了什么。

总结

综上所述,已经足够实现单独拉出来的接口了,掌握了基本的实现方法,其实很多问题也便迎刃而解了。由于开发经验和时间有限,以上代码可优化的部分还是很多的,作为个人学习路上的记录。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3