一、概述
JAXB能够使用Jackson对JAXB注解的支持实现(jackson-module-jaxb-annotations),既方便生成XML,也方便生成JSON,可以更好进行JavaBean和xml、JSON直接的互相转换。JAXB允许Java人员将Java类映射为xml表示方式,常用的注解包括:@XmlRootElement,@XmlElement等等。
JAXB(Java Architecture for XML Binding) 是一个业界的标准,是一项可以根据XML Schema产生Java类的技术。该过程中,JAXB也提供了将XML实例文档反向生成Java对象树的方法,并能将Java对象树的内容重新写到XML实例文档。从另一方面来讲,JAXB提供了快速而简便的方法将XML模式绑定到Java表示,从而使得Java开发者在Java应用程序中能方便地结合XML数据和处理函数。
二、常用的注解
注解 | 作用 |
@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属性 |
三、数据类型绑定
AXB中XML数据类型和Java数据类型的映射关系
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:QName | javax.xml.namespace.QName |
xsd:dateTime | javax.xml.datatype.XMLGregorianCalendar |
xsd:base64Binary | byte[] |
xsd:hexBinary | byte[] |
xsd:unsignedInt | long |
xsd:unsignedShort | int |
xsd:unsignedByte | short |
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 |
四、使用示例
4.1 添加依赖
<jaxb.version>4.0.1</jaxb.version>
<!-- JAXB 依赖 -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
</dependency>
4.2 模型
import lombok.Getter;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.time.LocalDate;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"level", "cityName", "cityCode", "creationTime"})
@XmlRootElement(name = "Project")
@Getter
@Setter
public class CityXmlInfo {
@XmlAttribute(name = "level")
private String level = "";
@XmlAttribute(name = "cityName ")
private String cityName = "";
@XmlAttribute(name = "cityCode ")
private String cityCode = "";
@XmlAttribute(name = "creationTime")
private String creationTime = LocalDate.now().toString();
}
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
/**
* JAXB测试模型 ProvincesXmlInfo
*
**/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"provinceXmlInfos"})
@XmlRootElement(name = "Provinces")
@Getter
@Setter
public class ProvincesXmlInfo {
@XmlElement(name = "province", required = true)
private List<ProvinceXmlInfo> provinceXmlInfos;
}
import lombok.Getter;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.time.LocalDate;
import java.util.List;
/**
* JAXB测试模型 ProvinceXmlInfo
*
**/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {"cityXmlInfo"})
@XmlRootElement(name = "Province")
@Getter
@Setter
public class ProvinceXmlInfo {
@XmlAttribute(name = "level")
private String level = "";
@XmlAttribute(name = "provinceName")
private String provinceName = "";
@XmlAttribute(name = "provinceCode")
private String provinceCode = "";
@XmlAttribute(name = "creationTime")
private String creationTime = LocalDate.now().toString();
@XmlElement(name = "city", required = true)
private List<CityXmlInfo> cityXmlInfo;
}
4.3 工具类
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamReader;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import lombok.extern.slf4j.Slf4j;
/**
* 功能描述:Jaxb工具类
*
**/
@Slf4j
public class JaxbUtils {
private static final String XML_FILE = ".xml";
private JaxbUtils() {
}
/**
* 将对象转为xml并写入{@code filePath}文件中, xml未格式化
*
* @param object 待转换写入的对象
* @param filePath 待写入的文件路径
* @param <T> 对象类型
* @return 是否成功写入
*/
public static <T> boolean marshal(T object, String filePath) {
if (object == null) {
return false;
}
OutputStream outputStream = null;
BufferedOutputStream bos = null;
XMLEventWriter xmlEventWriter = null;
boolean marshalResult = true;
try {
JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass());
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
File file = new File(filePath);
XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory();
outputStream = org.apache.commons.io.FileUtils.openOutputStream(file);
bos = new BufferedOutputStream(outputStream);
xmlEventWriter = xmlOutputFactory.createXMLEventWriter(bos, StandardCharsets.UTF_8.name());
marshaller.marshal(object, xmlEventWriter);
} catch (Exception e) {
log.error("{}", ExceptionUtils.getStackTrace(e));
marshalResult = false;
} finally {
IoUtils.close(xmlEventWriter);
IoUtils.close(bos, outputStream);
}
return marshalResult;
}
/**
* 将对象转为xml字符,并格式化,然后写入{@code filePath}文件中
*
* @param object 待转换写入的对象
* @param filePath 待写入的文件路径
* @param <T> 对象类型
* @return 是否成功写入
*/
public static <T> boolean marshalAndFormat(T object, String filePath) {
if (object == null) {
return false;
}
FileOutputStream fos = null;
boolean marshalResult = true;
try {
File file = new File(filePath);
fos = org.apache.commons.io.FileUtils.openOutputStream(file);
fos.write(objectConvertXml(object).getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
log.error("{}", ExceptionUtils.getStackTrace(e));
marshalResult = false;
} finally {
IoUtils.close(fos);
}
return marshalResult;
}
/**
* 将xml文件输入流,转换为Java对象
*
* @param inputStream 输入流
* @param clazz 待转换的类
* @param <T> 类型
* @return 转换的对象
*/
public static <T> Optional<T> xmlConvert2Object(InputStream inputStream, Class<T> clazz) {
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();
}
}
private static <T> String objectConvertXml(T obj) {
StringWriter sw = null;
try {
JAXBContext jaxbContext = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
sw = new StringWriter();
marshaller.marshal(obj, sw);
return sw.toString();
} catch (Exception e) {
log.error("{}", ExceptionUtils.getStackTrace(e));
return StringUtils.EMPTY;
} finally {
IoUtils.close(sw);
}
}
}
4.4 单元测试
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
import java.util.Optional;
import com.google.common.collect.Lists;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class JaxbUtilsTest {
@Test
void marshal() {
ProvincesXmlInfo provincesXmlInfo = new ProvincesXmlInfo();
ProvinceXmlInfo provinceXmlInfo = new ProvinceXmlInfo();
provinceXmlInfo.setLevel("0");
provinceXmlInfo.setProvinceCode("110000");
provinceXmlInfo.setProvinceName("北京市");
provinceXmlInfo.setCreationTime("2024-03-19");
CityXmlInfo cityXmlInfo = new CityXmlInfo();
cityXmlInfo.setCityCode("119900");
cityXmlInfo.setCityName("市辖区");
cityXmlInfo.setLevel("1");
cityXmlInfo.setCreationTime("2024-03-19");
List<CityXmlInfo> cityXmlInfos = Lists.newArrayList(cityXmlInfo);
provinceXmlInfo.setCityXmlInfo(cityXmlInfos);
provincesXmlInfo.setProvinceXmlInfos(Lists.newArrayList(provinceXmlInfo));
boolean marshalSuccess = JaxbUtils.marshal(provincesXmlInfo, "src/test/resources/jaxb/marshal/marshal_province.xml");
Assertions.assertTrue(marshalSuccess);
File file = new File("src/test/resources/jaxb/marshal/marshal_province.xml");
Assertions.assertTrue(file.exists());
}
@Test
void marshalAndFormat() {
ProvincesXmlInfo provincesXmlInfo = new ProvincesXmlInfo();
ProvinceXmlInfo provinceXmlInfo = new ProvinceXmlInfo();
provinceXmlInfo.setLevel("0");
provinceXmlInfo.setProvinceCode("110000");
provinceXmlInfo.setProvinceName("北京市");
provinceXmlInfo.setCreationTime("2024-03-19");
CityXmlInfo cityXmlInfo = new CityXmlInfo();
cityXmlInfo.setCityCode("119900");
cityXmlInfo.setCityName("市辖区");
cityXmlInfo.setLevel("1");
cityXmlInfo.setCreationTime("2024-03-19");
List<CityXmlInfo> cityXmlInfos = Lists.newArrayList(cityXmlInfo);
provinceXmlInfo.setCityXmlInfo(cityXmlInfos);
provincesXmlInfo.setProvinceXmlInfos(Lists.newArrayList(provinceXmlInfo));
boolean marshalSuccess =
JaxbUtils.marshalAndFormat(provincesXmlInfo, "src/test/resources/jaxb/marshal/marshalAndFormat_province.xml");
Assertions.assertTrue(marshalSuccess);
File file = new File("src/test/resources/jaxb/marshal/marshalAndFormat_province.xml");
Assertions.assertTrue(file.exists());
}
@Test
void xmlConvert2Object() throws FileNotFoundException {
InputStream inputStream = new FileInputStream("src/test/resources/jaxb/marshal/marshalAndFormat_province.xml");
Optional<ProvincesXmlInfo> optionalProvincesXmlInfo = JaxbUtils.xmlConvert2Object(inputStream, ProvincesXmlInfo.class);
Assertions.assertTrue(optionalProvincesXmlInfo.isPresent());
ProvincesXmlInfo provincesXmlInfo = optionalProvincesXmlInfo.get();
Assertions.assertEquals(1, provincesXmlInfo.getProvinceXmlInfos().size());
List<ProvinceXmlInfo> provinceXmlInfos = provincesXmlInfo.getProvinceXmlInfos();
ProvinceXmlInfo provinceXmlInfo = provinceXmlInfos.getFirst();
Assertions.assertEquals(1, provinceXmlInfo.getCityXmlInfo().size());
Assertions.assertEquals("北京市", provinceXmlInfo.getProvinceName());
}
@Test
void testXmlConvert2Object() {
Optional<ProvincesXmlInfo> optionalProvincesXmlInfo =
JaxbUtils.xmlConvert2Object("src/test/resources/jaxb/marshal/marshalAndFormat_province.xml", ProvincesXmlInfo.class);
Assertions.assertTrue(optionalProvincesXmlInfo.isPresent());
ProvincesXmlInfo provincesXmlInfo = optionalProvincesXmlInfo.get();
Assertions.assertEquals(1, provincesXmlInfo.getProvinceXmlInfos().size());
List<ProvinceXmlInfo> provinceXmlInfos = provincesXmlInfo.getProvinceXmlInfos();
ProvinceXmlInfo provinceXmlInfo = provinceXmlInfos.getFirst();
Assertions.assertEquals(1, provinceXmlInfo.getCityXmlInfo().size());
Assertions.assertEquals("北京市", provinceXmlInfo.getProvinceName());
}
}
测试结果
- marshal_province.xml
<?xml version="1.0" encoding="UTF-8"?><Provinces><province level="0" provinceName="北京市" provinceCode="110000" creationTime="2024-03-19"><city level="1" cityName="市辖区" cityCode="119900" creationTime="2024-03-19"></city></province></Provinces>
- marshalAndFormat_province.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Provinces>
<province level="0" provinceName="北京市" provinceCode="110000" creationTime="2024-03-19">
<city level="1" cityName="市辖区" cityCode="119900" creationTime="2024-03-19"/>
</province>
</Provinces>