在Java中,XML序列化(Serialization)是指将Java对象的状态转换为XML格式的数据,以便存储或传输。而反序列化(Deserialization)则是将XML格式的数据转换回Java对象的过程。这一机制在数据交换、配置文件的读取与写入等方面有着广泛的应用。Java提供了多种方式来实现XML的序列化和反序列化,其中较为常用的有JAXB(Java Architecture for XML Binding)和DOM、SAX等解析技术。
方式一:JAXB
介绍
JAXB通过利用Jackson对JAXB注解的支持(即jackson-module-jaxb-annotations模块),不仅简化了XML的生成过程,同时也便于生成JSON。这种集成方式极大地增强了JavaBean与XML及JSON之间的互操作性,使得数据转换变得更加灵活高效。
JAXB(Java Architecture for XML Binding)作为业界标准,它提供了一种机制,可以根据XML Schema自动生成对应的Java类。同时,JAXB也支持将XML文档实例转换为Java对象树,以及将Java对象树的内容重新序列化为XML文档。这一特性为Java开发者提供了一种快捷、简便的方式,将XML模式与Java表示进行绑定,从而在Java应用中能够轻松地处理XML数据。
在JAXB中,常用的注解如@XmlRootElement和@XmlElement等,为开发者提供了丰富的手段来定制Java类与XML之间的映射关系。这些注解的使用,进一步简化了Java类与XML之间的转换过程,提高了开发效率。
优缺点
优点:
-
简化XML处理:JAXB能够自动将Java对象和XML文档进行相互转换,减少了手动解析和创建XML文档的工作量,使开发者能够更专注于业务逻辑。
-
提高代码可读性和可维护性:通过使用注解来描述Java对象和XML之间的映射关系,JAXB使代码更加清晰易懂,降低了维护成本。
-
支持标准化数据交换格式:JAXB支持标准的XML数据交换格式,便于与其他使用XML的系统进行交互,增强了系统的互操作性。
-
灵活性和扩展性:JAXB提供了丰富的注解和API,支持复杂的XML结构,并且可以轻松地扩展以支持自定义的数据处理需求。
缺点:
-
学习成本:使用JAXB需要了解XML和Java对象之间的映射关系,以及如何使用JAXB注解来描述这种映射,这可能需要一定的学习时间和实践经验。
-
性能问题:在处理大规模XML数据时,JAXB的解析和创建过程可能会消耗较多的时间和内存资源,导致性能瓶颈。
-
依赖性:JAXB是Java平台的一部分,因此它的使用受限于Java环境,对于非Java平台可能需要额外的桥接技术。
常用注解
注解 | 作用 |
---|---|
@XmlType | 将Java类或枚举类型映射到XML模式类型 |
@XmlAccessorType(XmlAccessType.FIELD) | 控制字段或属性的序列化。FIELD表示JAXB将自动绑定Java类中的每个非静态的(static)、非瞬态的(由@XmlTransient标注)字段到XML。其他值还有XmlAccessType.PROPERTY和XmlAccessType.NONE |
@XmlAccessorOrder | 控制JAXB 绑定类中属性和字段的排序 |
@XmlJavaTypeAdapter | 使用定制的适配器(即扩展抽象类XmlAdapter并覆盖marshal()和unmarshal()方法),以序列化Java类为XML |
@XmlElementWrapper | 对于数组或集合(即包含多个元素的成员变量),生成一个包装该数组或集合的XML元素(称为包装器) |
@XmlRootElement | 将Java类或枚举类型映射到XML元素 |
@XmlElement | 将Java类的一个属性映射到与属性同名的一个XML元素 |
@XmlAttribute | 将Java类的一个属性映射到与属性同名的一个XML属性 |
数据类型绑定
XML Schema类型 | Java数据类型 |
---|---|
xsd:string | java.lang.String |
xsd:positiveInteger | java.math.BigInteger |
xsd:int | int |
xsd:long | long |
xsd:short | short |
xsd:decimal | java.math.BigDecimal |
xsd:float | float |
xsd:double | double |
xsd:boolean | boolean |
xsd:byte | byte |
xsd:!Name | javax.xml.namespace.QName |
xsd:dateTime | javax.xml.datatype.XMLGregorianCalendar |
xsd:base64Binary | byte[] |
xsd:hexBinary | byte[] |
xsd:unsignedInt | long |
xsd:unsignedShort | int |
xsd:unsignedByte | shirt |
xsd:time | javax.xml.datatype.XMLGregorianCalendar |
xsd:date | javax.xml.datatype.XMLGregorianCalendar |
xsd:g | javax.xml.datatype.XMLGregorianCalendar |
xsd:anySimpleType | java.lang.Object |
xsd:anySimpleType | java.lang.String |
xsd:duration | javax.xml.datatype.Duration |
xsd:NOTATION | javax.xml.namespace.QName |
使用
添加依赖
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>4.0.5</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.1</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
入门demo
为了使用JAXB,你需要在Java类上使用特定的注解来指定如何映射到XML。例如:
package com.zhz.test.serialization.entity;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@NoArgsConstructor
@XmlRootElement
public class Person {
private String name;
private Integer age;
@XmlElement
public String getName() {
return name;
}
@XmlElement
public Integer getAge() {
return age;
}
}
序列化
使用JAXB的Marshaller
类将Java对象序列化为XML字符串或文件。
//依赖
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
//测试代码
@Test
public void testMarshal() throws JAXBException {
Person person = new Person();
person.setName("John Doe");
person.setAge(30);
JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// 序列化到控制台
marshaller.marshal(person, System.out);
// 序列化到文件
marshaller.marshal(person, new File("D:/ideaproject/test/src/test/resources/person.xml"));
}
反序列化
使用JAXB的Unmarshaller
类将XML字符串或文件反序列化为Java对象。
通过字符串读取xml文件
//依赖
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
//测试代码
@Test
public void testUnmarshal() throws JAXBException, FileNotFoundException {
String xml = "<person><name>John Doe</name><age>30</age></person>";
JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
// 从字符串中读取(实际应用中可能从文件或网络读取)
StringReader reader = new StringReader(xml);
Person person =(Person) unmarshaller.unmarshal(reader);
System.out.println("Name: " + person.getName() + ", Age: " + person.getAge());
}
通过文件读取xml文件
@Test
public void testUnmarshal() throws JAXBException, FileNotFoundException {
JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
//通过文件读取xml配置
InputStream inputStream = new FileInputStream("D:/ideaproject/test/src/test/resources/person.xml");
Person person =(Person) unmarshaller.unmarshal(inputStream);
System.out.println("Name: " + person.getName() + ", Age: " + person.getAge());
}
高级用法
实体类
package com.zhz.test.serialization.entity;
import jakarta.xml.bind.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 城市集合
*
* @author zhouhengzhe
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@XmlRootElement(name = "Project")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"level", "cityName", "cityCode", "creationTime"})
public class City {
@XmlAttribute(name = "level")
private Integer level;
@XmlAttribute(name = "cityName ")
private String cityName;
@XmlAttribute(name = "cityCode ")
private String cityCode;
@XmlAttribute(name = "creationTime")
private String creationTime;
}
package com.zhz.test.serialization.entity;
import jakarta.xml.bind.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
*
* 省份信息
*
* @author zhouhengzhe
*
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@XmlRootElement(name = "Province")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"city"})
public class Province {
@XmlAttribute(name = "level")
private Integer level ;
@XmlAttribute(name = "provinceCode")
private String provinceCode;
@XmlAttribute(name = "provinceName")
private String provinceName;
@XmlAttribute(name = "creationTime")
private String creationTime;
@XmlElement(name = "city", required = true)
private List<City> city;
}
package com.zhz.test.serialization.entity;
import jakarta.xml.bind.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 多个省份信息集合
*
* @author zhouhengzhe
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@XmlRootElement(name = "Provinces")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"province"})
public class Provinces {
@XmlElement(name = "province", required = true)
private List<Province> provinces;
}
工具类
package com.zhz.test.serialization.xml.jaxb;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
/**
* Jaxb工具类
*
* @author zhouhengzhe
**/
@Slf4j
public class JaxbUtils {
private static final String XML_FILE = ".xml";
private JaxbUtils() {
}
/**
* 将对象转为xml并写入{@code filePath}文件中, xml未格式化
*
* @param object 待转换写入的对象
* @param filePath 待写入的文件路径
* @param isFormatFlag 是否格式化
* @param <T> 对象类型
* @return 是否成功写入
*/
public static <T> boolean marshal(T object, String filePath,Boolean isFormatFlag) {
if (object == null) {
return false;
}
boolean marshalResult = true;
try {
JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass());
Marshaller marshaller = jaxbContext.createMarshaller();
if (isFormatFlag){
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
}
marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
File file = new File(filePath);
marshaller.marshal(object, file);
} catch (Exception e) {
log.error("{}", ExceptionUtils.getStackTrace(e));
marshalResult = false;
}
return marshalResult;
}
/**
* 将xml文件输入流,转换为Java对象
*
* @param inputStream 输入流
* @param clazz 待转换的类
* @param <T> 类型
* @return 转换的对象
*/
public static <T> Optional<T> xmlConvert2Object(InputStream inputStream, Class<T> clazz) throws IOException {
InputStreamReader inputStreamReader = null;
Object obj = null;
try {
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
XMLStreamReader xmlStreamReader = xmlInputFactory.createXMLStreamReader(inputStreamReader);
obj = unmarshaller.unmarshal(xmlStreamReader);
} catch (Exception e) {
log.warn("xmlConvert2Object fail, ", e);
} finally {
IOUtils.close(inputStreamReader);
IOUtils.close(inputStream);
}
if (clazz.isInstance(obj)) {
return Optional.of(clazz.cast(obj));
}
return Optional.empty();
}
/**
* 将xml文件转换为Java对象
*
* @param filePath 待转换xml对象的xml文件路径
* @param clazz 待转换的类
* @param <T> 类型
* @return 转换的对象
*/
public static <T> Optional<T> xmlConvert2Object(String filePath, Class<T> clazz) {
if (StringUtils.isEmpty(filePath)) {
return Optional.empty();
}
if (!StringUtils.endsWithIgnoreCase(filePath, XML_FILE)) {
return Optional.empty();
}
File file = new File(filePath);
if (!file.exists()) {
return Optional.empty();
}
try {
InputStream inputStream = org.apache.commons.io.FileUtils.openInputStream(file);
return xmlConvert2Object(inputStream, clazz);
} catch (Exception e) {
log.error("{}", ExceptionUtils.getStackTrace(e));
return Optional.empty();
}
}
}
测试用例
@Test
public void marshal() {
Provinces provinces = Provinces.builder()
.provinces(Lists.newArrayList(Province.
builder()
.level(1)
.provinceCode("2")
.provinceName("广东省")
.creationTime(LocalDateTime.now().toString())
.city(Lists.newArrayList(City
.builder()
.level(1)
.cityCode("1")
.cityName("广州市")
.creationTime(LocalDateTime.now().toString())
.build()))
.build()))
.build();
boolean marshalSuccess = JaxbUtils.marshal(provinces, "src/test/resources/jaxb/marshal/marshal_province.xml", false);
Assertions.assertTrue(marshalSuccess);
File file = new File("src/test/resources/jaxb/marshal/marshal_province.xml");
Assertions.assertTrue(file.exists());
}
@Test
public void marshalAndFormat() {
Provinces provinces = Provinces.builder()
.provinces(Lists.newArrayList(Province.
builder()
.level(1)
.provinceCode("2")
.provinceName("广东省")
.creationTime(LocalDateTime.now().toString())
.city(Lists.newArrayList(City
.builder()
.level(1)
.cityCode("1")
.cityName("广州市")
.creationTime(LocalDateTime.now().toString())
.build()))
.build()))
.build();
boolean marshalSuccess =
JaxbUtils.marshal(provinces, "src/test/resources/jaxb/marshal/marshalAndFormat_province.xml", true);
Assertions.assertTrue(marshalSuccess);
File file = new File("src/test/resources/jaxb/marshal/marshalAndFormat_province.xml");
Assertions.assertTrue(file.exists());
}
@Test
public void xmlConvert2Object() throws IOException {
InputStream inputStream = new FileInputStream("src/test/resources/jaxb/marshal/marshalAndFormat_province.xml");
Optional<Provinces> optionalProvincesXmlInfo = JaxbUtils.xmlConvert2Object(inputStream, Provinces.class);
Assertions.assertTrue(optionalProvincesXmlInfo.isPresent());
Provinces provinces = optionalProvincesXmlInfo.get();
Assertions.assertEquals(1, provinces.getProvinces().size());
List<Province> provinceXmlInfos = provinces.getProvinces();
Province province = provinceXmlInfos.get(0);
Assertions.assertEquals(1, province.getCity().size());
Assertions.assertEquals("广东省", province.getProvinceName());
}
@Test
public void testXmlConvert2Object() {
Optional<Provinces> optionalProvincesXmlInfo =
JaxbUtils.xmlConvert2Object("src/test/resources/jaxb/marshal/marshalAndFormat_province.xml", Provinces.class);
Assertions.assertTrue(optionalProvincesXmlInfo.isPresent());
Provinces provinces = optionalProvincesXmlInfo.get();
Assertions.assertEquals(1, provinces.getProvinces().size());
List<Province> provinceXmlInfos = provinces.getProvinces();
Province province = provinceXmlInfos.get(0);
Assertions.assertEquals(1, province.getCity().size());
Assertions.assertEquals("广东省", province.getProvinceName());
}
2、方式二:DOM4j
介绍
DOM4J是一个优秀的Java XML API,它具有性能优异、功能强大和易于使用的特点。在SpringBoot项目中,你可以轻松地集成DOM4J来处理XML数据。
优缺点
优点:
-
高性能:DOM4J在处理大型XML文件时表现出色,提供了高效的解析和操作能力。
-
易于使用:DOM4J的API设计简洁明了,易于上手。它大量使用了Java集合类,使得Java开发人员能够更加方便地操作XML数据。
-
支持XPath:DOM4J支持XPath表达式,这使得查询XML文档中的特定元素变得更加简单。
缺点:
- API较为复杂:虽然DOM4J提供了丰富的功能,但其API相对较为复杂,可能需要一定的学习成本。
场景
-
XML解析与生成:当你需要读取或生成XML文件时,可以使用DOM4J来解析XML数据或创建新的XML文档。
-
XML数据操作:DOM4J提供了灵活的API来操作XML数据,包括添加、删除、修改和查询元素等。
-
与第三方系统交互:当与使用XML作为数据交换格式的第三方系统进行交互时,DOM4J可以帮助你处理XML请求和响应。
常用方法
- 创建SAXReader:使用
SAXReader
类来创建一个XML解析器实例。
SAXReader reader = new SAXReader();
- 读取XML文件:使用
SAXReader
的read
方法来读取XML文件,并返回一个Document
对象。
Document document = reader.read(file);
- 获取根元素:通过
Document
对象的getRootElement
方法来获取XML文档的根元素。
Element root = document.getRootElement();
- 遍历元素:使用
elementIterator
或elements
方法来遍历XML文档中的元素。
for (Element element : root.elements()) {
// 处理元素
}
- 获取和设置元素属性:使用
attributeValue
方法来获取元素的属性值,使用addAttribute
方法来设置元素的属性。
String value = element.attributeValue("attrName");
element.addAttribute("attrName", "newValue");
- 获取和设置元素文本:使用
getText
方法来获取元素的文本内容,使用setText
方法来设置元素的文本。
String text = element.getText();
element.setText("newText");
使用
添加依赖
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
实体类
package com.zhz.test.serialization.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Employee {
private int id;
private String name;
private String position;
}
序列化
序列化成文件或者字符串
/**
* 序列化
*/
@Test
public void testMarshal() {
// 创建一个Employee对象
Employee employee = new Employee();
employee.setId(1);
employee.setName("John Doe");
employee.setPosition("Developer");
//序列化成字符串
String xml = serializeEmployeeToStr(employee);
System.out.println(xml);
//序列化成文件
serializeEmployeeToFile(employee,"src/test/resources/dom4j/employeeToSer.xml");
}
/**
* 序列化成字符串
* @param employee
* @return
*/
public static String serializeEmployeeToStr(Employee employee) {
// 创建XML文档和根元素
Document document = DocumentHelper.createDocument();
Element root = document.addElement("employees");
// 创建并填充employee元素
Element employeeElement = root.addElement("employee")
.addAttribute("id", String.valueOf(employee.getId()));
employeeElement.addElement("name").addText(employee.getName());
employeeElement.addElement("position").addText(employee.getPosition());
// 序列化XML到字符串
StringWriter writer = new StringWriter();
XMLWriter xmlWriter = new XMLWriter(writer);
try {
xmlWriter.write(document);
xmlWriter.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
return writer.toString();
}
/**
* 序列化成字符串
* @param employee
* @return
*/
public static void serializeEmployeeToFile(Employee employee,String filePath) {
// 创建XML文档和根元素
Document document = DocumentHelper.createDocument();
Element root = document.addElement("employees");
// 创建并填充employee元素
Element employeeElement = root.addElement("employee")
.addAttribute("id", String.valueOf(employee.getId()));
employeeElement.addElement("name").addText(employee.getName());
employeeElement.addElement("position").addText(employee.getPosition());
File file = new File(filePath);
// 序列化XML到字符串
try {
XMLWriter xmlWriter = new XMLWriter(new FileOutputStream(file));
xmlWriter.write(document);
xmlWriter.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
反序列化
employees.xml的配置内容如下
<?xml version="1.0" encoding="UTF-8"?>
<employees>
<employee id="1">
<name>John Doe</name>
<position>Developer</position>
</employee>
<employee id="2">
<name>Jane Smith</name>
<position>Manager</position>
</employee>
</employees>
/**
* 反序列化
*/
@Test
public void testUnmarshal() {
String filePath = "src/test/resources/dom4j/employees.xml";
Iterator<Element> iterator = parseXmlFile(filePath);
List<Employee> employees = Lists.newArrayList();
while (iterator.hasNext()) {
Element employeeElement = iterator.next();
employees.add(Employee
.builder()
.id(Integer.parseInt(employeeElement.attributeValue("id")))
.name(employeeElement.elementText("name"))
.position(employeeElement.elementText("position"))
.build());
}
System.out.println("反序列化得到的数据为:" + employees);
}
public Iterator<Element> parseXmlFile(String filePath) {
try {
File xmlFile = new File(filePath);
SAXReader reader = new SAXReader();
Document document = reader.read(xmlFile);
Element root = document.getRootElement();
return root.elementIterator();
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
打个号外
本人新搞的个人项目,有意者可到 DDD用户中台 这里购买
可以学习到的体系
-
项目完全从0到1开始架构,包含前端,后端,架构,服务器,技术管理相关运维知识!
- 最佳包名设计,项目分层
-
破冰CRUD,手撕中间件!
-
基于MybatisPlus封装属于自己的DDD ORM框架
-
基于Easyexcel封装属于自己的导入导出组件
-
oss对象存储脚手架(阿里云,minio,腾讯云,七牛云等)
-
邮件脚手架
-
completefuture脚手架
-
redis脚手架
-
xxl-job脚手架
-
短信脚手架
-
常用工具类等
-
-
传统MVC代码架构弊端的解决方案
- DDD+CQRS+ES最难架构
-
结合实际代码的业务场景
-
多租户单点登录中心
-
用户中台
-
消息中心
-
配置中心
-
监控设计
-
-
程序员的职业规划,人生规划
-
打工永远没有出路!
-
打破程序员的35岁魔咒
-
技术带给你的优势和竞争力【启发】
-
万物互联网的淘金之路!
-
技术以外的赚钱路子
可以一起沟通
具体的文章目录