Spring boot项目实现java bean和xml互转
- 项目场景:
- 互转方法
- 使用`jackson`进行互转
- 使用`jaxws`进行`xml`与`bean`的互转
- 搞定收工!
项目场景:
工作中需要给下游第三方收费系统做数据挡板,由于下游系统使用的是soap webservice
,里面涉及各种xml跟bean的互转,在此介绍一下使用的方法。
基于springboot搭建webservice的过程将会在下篇博客介绍
互转方法
这里介绍两种方法.
- 使用
jackson
进行互转,Spring boot
项目自带的json
和bean
的互转的框架,他其实还有xml
与bean
的互转。 - 使用
jaxws
进行xml
与bean
的互转
使用jackson
进行互转
因为Spring Boot 项目其他依赖基本上都会引入,只是缺少一个依赖
com.fasterxml.jackson.dataformat:jackson-dataformat-xml
,所以只需要引入这一个依赖即可。
- gradle引入
implementation('com.fasterxml.jackson.dataformat:jackson-dataformat-xml')
- maven 引入
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.13.5</version></dependency>
- 可以做成一个通用的XMLUtils工具类,代码如下:
public static String javaBean2Xml(Object javaBean) throws JsonProcessingException {XmlMapper xmlMapper = new XmlMapper();xmlMapper.setDefaultUseWrapper(false);//字段为null就自动忽略,不再序列化xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);//XML标签名:使用骆驼命名的属性名,xmlMapper.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE);//设置转换模式,就是根据getter、setter方法,设置为第一个字母小写这种xmlMapper.enable(MapperFeature.USE_STD_BEAN_NAMING);return xmlMapper.writeValueAsString(javaBean);}public static <T> T Xml2javaBean(String javaBean, Class<T> tClass) throws JsonProcessingException {XmlMapper xmlMapper = new XmlMapper();xmlMapper.setDefaultUseWrapper(false);//字段为null,自动忽略,不再序列化xmlMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);//XML标签名:使用骆驼命名的属性名,xmlMapper.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE);//设置转换模式,就是根据getter、setter方法,设置为第一个字母小写这种xmlMapper.enable(MapperFeature.USE_STD_BEAN_NAMING);return xmlMapper.readValue(javaBean, tClass);}
- POJO类,后续都会用这个做例子
public class Student {private String name;private String teacher;private Integer age;....省略构造函数和getter和setter
}
- 测试案例
public static void main(String[] args) throws JsonProcessingException {Student student = new Student("张三","张老师", 26);String s = XMLUtils.javaBean2Xml(student);System.out.println(s);System.out.println(XMLUtils.Xml2javaBean(s, Student.class));
}
结果:
- 可能会出现的bug
在使用
jackson
去进行转换的时候,POJO类不管几个构造函数,一定要有无参构造,否则就会报错。
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance ofStudent
(no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
- 如果想给POJO的属性的XML起个别名怎么办,
jackson
是提供了相关的注解。
- @JacksonXmlRootElement
namespace属性:用于指定XML根元素命名空间的名称。
localname属性:用于指定XML根元素节点标签的名称。- @JacksonXmlProperty
namespace和localname属性用于指定XML命名空间的名称,isAttribute指定该属 性作为XML的属性()还是作为子标签().- @JacksonXmlText注解将属性直接作为未被标签包裹的普通文本。
- @JacksonXmlCData将属性包裹在CDATA标签中。
- 集合元素的映射
@JacksonXmlElementWrapper可以将列表数据转为XML节点。
useWrapping属性设置是否设置外围标签名,默认true
其他的可以自己尝试下,如果是List的集合的,是以一对多的一对应的,一里面定义了XML节点,就以一里面的定义为主。
-
补充完整的依赖,和
Spring Boot
已经引入的依赖。- 完整依赖
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.13.5</version> </dependency> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.5</version> </dependency> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.13.5</version> </dependency> <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.13.5</version> </dependency>
Spring Boot
已经引入的依赖,以2.6.12为例
- 完整依赖
使用jaxws
进行xml
与bean
的互转
使用起来基本是跟
jackson
差不多的。好处就是jdk自己就有提供,不需要引入额外的工具包,主要是使用javax
下的JAXBContext
接口,利用Marshaller
和Unmarshaller
接口来进行xml
与bean
的互转。
但是需要注意的是
- 必须要提供
无参构造器
,否则会报错com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions Student没有无参数默认构造器。- 必须在类上提供@javax.xml.bind.annotation.XmlRootElement,否则会报错javax.xml.bind.MarshalException
-with linked exception:
[com.sun.istack.SAXException2: 由于类型 “Student” 缺少 @XmlRootElement 注释, 无法将该类型编集为元素]- 如果不提供@javax.xml.bind.annotation.XmlElement注解,那么所产生的XML节点均为属性值,就是小写。
- @javax.xml.bind.annotation.XmlElement定义的值不能跟getter方法一起出现,否则会报错com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
类的两个属性具有相同名称 “name”
- 老规矩,工具类
public static String beanToXml (Object obj, Class<?> zlass) throws JAXBException {JAXBContext context = JAXBContext.newInstance(zlass);Marshaller marshaller = context.createMarshaller();marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);marshaller.setProperty(Marshaller.JAXB_ENCODING, "GBK");marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);StringWriter writer = new StringWriter();marshaller.marshal(obj,writer);return writer.toString();}public static <T> T xmlToBean (String xml, Class<T> zlass) throws JAXBException {JAXBContext context = JAXBContext.newInstance(zlass);Unmarshaller unmarshaller = context.createUnmarshaller();Object object = unmarshaller.unmarshal(new StringReader(xml));return (T) object;}
- Marshaller 接口中还定义了5个属性,分别是:
- JAXB_ENCODING
这个属性是设置编码集,
marshaller.setProperty(Marshaller.JAXB_ENCODING, “GBK”);- JAXB_FORMATTED_OUTPUT
这个属性是是否格式化生成的xml串 true-格式化,false-不格式化
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);- JAXB_SCHEMA_LOCATION
指定xsi:schemaLocation,它定义了XML Namespace和对应的XSD(Xml Schema Definition)文档的位置的关系。它的值由一个或多个URI引用对组成,两个URI之间以空白符分隔(空格和换行均可)。第一个URI是定义的XML Namespace的值,第二个URI给出Schema文档的位置,Schema处理器将从这个位置读取Schema文档,该文档的targetNamespace必须与第一个URI相匹配。
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, “xxx.xxx.xxx”);- JAXB_NO_NAMESPACE_SCHEMA_LOCATION
如果没有Namespeace,但是需要使用Schema,就需要用到JAXB_NO_NAMESPACE_SCHEMA_LOCATION,它可以指定将放置在已编组 XML 输出中的 xsi:noNamespaceSchemaLocation 属性值
marshaller.setProperty(Marshaller.JAXB_NO_NAMESPACE_SCHEMA_LOCATION, “xxx.xxx.xxx”);- JAXB_FRAGMENT
是否省略xml头信息()true-省略,false-不省略
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);
- 代码
public static void main(String[] args) throws Exception {Student student = new Student("张三","张老师", 26);String s = XMLUtils.beanToXml(student, Student.class);System.out.println(s);System.out.println(XMLUtils.xmlToBean(s, Student.class));
}
- 测试
- 最后补充几个注解
- @XmlRootElement
类级别的注解,这个注解为根节点的注解,加在类上面,而且为必要的注解,如果没有此注解,执行beanToXml方法时将会报异常。这个根节点默认名字为类名,但是可以设置name属性来修改根节点名字。namespace属性可以用于指定生成的元素所属的命名空间。- @XmlElement
字段,方法级别的注解,将java类的属性映射为xml的一个结点。一般使用在属性上,或者get方法上,其中常用的属性有name、nillable、namespace、defaultValue。name可以设置结点的名称;nillable 指定文本是否可以为空,true-可以为空,false-不可以为空,默认为false,如果设置为true,则该字段为空是,这个结点也会生成,但是值为空,如果是指为false,则该结点不生成;namespace属性可以用于指定生成的元素所属的命名空间;defaultValue 可以设置该结点的默认文本。- @XmlTransient
类,字段,方法级别的注解。当添加这个注解后,这个属性或者类将不进行映射。需要注意的是该注解与所有其他JAXB注解相互排斥.- @XmlAccessorType
类级别注解,其中有一个value属性,值为XmlAccessType的枚举类。
1.XmlAccessType.PROPERTY 加这个value表示,会将所有拥有get方法和set方法的属性(必须2个方法都有,否则不映射)映射成xml,除非加入@XmlTransient则不会映射,如果没有get/set方法,则需要再属性上加上@XmlElement。
2.XmlAccessType.FIELD
这个属性是将类中非静态的属性都映射到xml中,并且不需要加get/set方法
3.XmlAccessType.PUBLIC_MEMBER
这个属性值,是@XmlAccessorType的默认默认值,它会将属性为public的属性或者get/set方法同时为public的属性映射成xml。
4.XmlAccessType.NONE
这个属性表示任何属性都不会被映射到xml中,除非使用其他注解,如@XmlElement- @XmlAccessorOrder
类级别的注解。控制生成属性映射xml结点的顺序。其中有一个value属性,可以设置排序方式,XmlAccessOrder.ALPHABETICAL 为按照字母顺序进行排序, XmlAccessOrder.UNDEFINED按照属性顺序进行排序,默认为XmlAccessOrder.UNDEFINED- @XmlJavaTypeAdapter
这个注解主要是解决一些数据格式化问题的,比如时间格式化。- @XmlElementWrapper
这个注解是加在集合上面的- @XmlAttribute
这个注解会将属性变为上一个结点的属性- @XmlType
类级别的注解,这个注解可以自定义排序,使用propOrder 属性。