复杂xml解析-JAXB
一. 背景
近期因为结束无人机航线规划, 大量结束kml文件(近似xml的文件), 需要解析复杂xml, 了解到一些xml的解析工具,
- 学习关键点:
- 解析
- 编写
- 约束文件(DTD文件[.dtd], Schema文件[.xsd]), 即常用的命名空间
- 验证xml是否符合约束文件
- 常见解析工具
- JDOM(不常用): 底层也是JAXP
- DOM4J: java比较流行的框架所选
- JAXB: 特点使用注解, 使用简单
- JAXP: 原生, 按标签解析或通过SAX进行解析, SAX与easy excel差不多都是按行解析, 如果不做修改, 这种方法占用内存较少
DOM4J需要单独引入依赖, 其他三种, 作者目前使用jdk8完全可以使用(根据不同版本会有不同)
- xml的验证
- DOM4J
- JAXP
二. 使用
目前遇到的都是复杂xml, 主要使用JAXB解析文件, 其他几种都需要写代码, 匹配标签进行实体类的封装
1. 注解使用
@XmlRootElement
- 作用和用法:
类级别的注解,将类映射为根元素
。 - 属性:
该注解含有name和namespace两个属性。- namespace属性用于指定生成的元素所属的命名空间。
- name属性用于指定生成元素的名字,若不指定则默认使用类名小写作为元素名。
注: 如果类型中使用多个命名空间, 建议所有@XmlRootElement、@Xmllement注解表明命名空间
@XmlElement
-
作用和用法:
字段,方法,参数级别的注解。该注解可以将被注解的字段
(非静态),或者被注解的get/set方法对应的字段映射为本地元素,也就是子元素。
默认使用字段名或get/set方法去掉前缀剩下部分小写作为元素名(在字段名和get/set方法符合命名规范的情况下)。 -
属性:
该注解的属性常用的属性有有:name、nillable、required、namespace、defaultValue- name属性可以指定生成元素的名字,同@XmlRootElement注解的name属性一样,不再举例。
- namespace属性用于指定生成的元素所属的命名空间。
- nillable属性可以指定元素的文本值是否可以为空,默认为false。
@XmlAttribute
- 作用和用法:
字段和方法级别的注解。该注解会将字段或get/set方法对应的字段映射成本类对应元素的属性
,属性名默认使用字段名或get/set方法去掉前缀剩下部分首字母小写(在字段名和get/set方法符合命名规范的情况下)。 - 属性:
该注解有name,required,namespace三个属性。用法和@XmlElement注解相同,不再举例。
@XmlTransient
- 作用和用法:
类,字段,方法级别的注解。可使JAXB在映射xml元素时忽略
被注解的类,字段,get/set对应字段。需要注意的是该注解与所有其他JAXB注释相互排斥,也就是说与其他注释连用就会报错。修改上面例子:
@XmlAccessorType
- 作用和用法:
包和类级别的注解。javaEE的API对该注解的解释是:控制字段是否被默认序列化。通俗来讲,就是决定哪些字段或哪些get/set方法对应的字段会被映射为xml元素,需要注意的是字段或get/set方法的访问权限(public/private)会影响字段是否被映射为xml元素,下面会详细讲解。
注: 主要与@XmlElement位置有关
关键属性:
- XmlAccessType.PROPERTY: 默认, 一般@XmlElement标注在类中getter或setter方法上
- XmlAccessType.FIELD: 一般@XmlElement标注在类属性上
@XmlSeeAlso
- 作用和用法
类注解, 用在父类中, 表明其下所有子类
@XmlType
- 作用和用法
类注解, 用于表明一个类的引用, 以及类下属性的解析顺序, 一般用于继承类的识别
-属性
name: 引用名称
propOrder: 属性组
@XmlElementRef
- 作用和用法
属性或方法注解, 用与表明引用的类, 主要与@XmlType配合使用, name一致, 一般用于继承类的引用
-属性
name: @XmlType的name
namespace: 命名空间
2. 示例
2.1 基本类
Template类
@Data
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "TemplateType", propOrder = {
"document"
})
@XmlRootElement(name = "template", namespace = "http://www.abc.cn")
public class Template implements Cloneable {
@XmlElementRef(name = "Document", namespace = "http://www.abc.cn")
protected BaseDocumentType document;
}
BaseDocument类
@Data
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BaseDocumentType", propOrder = {
"author",
"createTime",
"updateTime",
})
@XmlSeeAlso({
TemplateDocument.class,
WaylineDocument.class
})
@XmlRootElement(name = "Document", namespace = "http://www.abc.cn")
public abstract class BaseDocument {
@XmlElement(name = "author", namespace = "http://www.abc.cn")
protected String author;
@XmlElement(name = "createTime", namespace = "http://www.abc.cn")
protected Long createTime;
@XmlElement(name = "updateTime", namespace = "http://www.abc.cn")
protected Long updateTime;
}
TemplateDocument类
@Data
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "TemplateDocumentType", propOrder = {
"folder"
})
@XmlRootElement(name = "Document", namespace = "http://www.abc.cn")
public abstract class TemplateDocument extends BaseDocument {
@XmlElement(name = "Folder", namespace = "http://www.def.cn")
protected String folder;
}
WaylineDocument类
@Data
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "TemplateDocumentType", propOrder = {
"folder"
})
@XmlRootElement(name = "Document", namespace = "http://www.abc.cn")
public abstract class WaylineDocument extends BaseDocument {
@XmlElement(name = "Wayline", namespace = "http://www.def.cn")
protected String Wayline;
}
@XmlElementRef(name = “Document”, namespace = “http://www.abc.cn”)
protected BaseDocumentType document;
这段代码, 会根据@XmlSeeAlso标识的类, 根据标签不同自动匹配
2.2工具类
@Slf4j
public class KMLUtil {
/** xml 转 对象 */
public static Template parse(Class<?> clazz, InputStream inputStream) throws JAXBException {
log.debug("[param:parse]==>clazz:{}, inputStream:{}", clazz, inputStream);
JAXBContext jaxbContext = JAXBContext.newInstance(clazz);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return (WayLine)unmarshaller.unmarshal(inputStream);
}
/** 对象 转 xml */
public static void format(Template template, String path) throws JAXBException, IOException {
log.debug("[param:parse]==>wayline:{}, path:{}", template, path);
JAXBContext jaxbContext = JAXBContext.newInstance(template.getClass());
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// 禁用默认的XML声明
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
File file = new File(path);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
if (!file.exists()) {
file.createNewFile();
} else {
file.delete();
file.createNewFile();
}
StringWriter sw = new StringWriter();
sw.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
marshaller.marshal(wayline, sw);
FileWriter fw = new FileWriter(path);
fw.write(sw.toString());
fw.flush();
fw.close();
}
}