前言

  Jaxb确实是xml和java对象映射互转的一大利器. 但是在处理CData内容块的时候, 还是有些小坑. 结合网上搜索的资料, 本文提供了一种解决的思路, 看看能否优雅地解决CData产出的问题.

常规做法

  网上最常见的做法是借助XmlAdapter和CharacterEscapeHandler(sun的api)组合来实现.
  首先定义CDataAdapter类, 用于对象类型转换.

public class CDataAdapter extends XmlAdapter<String, String> {

    @Override
public String unmarshal(String v) throws Exception {
return v;
} @Override
public String marshal(String v) throws Exception {
return new StringBuilder("<![CDATA[").append(v).append("]]>").toString();
} }

  其借助注解XmlJavaTypeAdapter作用于属性变量上, 如下面的类对象上:

@XmlRootElement(name="root")
public static class TNode { @XmlJavaTypeAdapter(value=CDataAdapter.class)
@XmlElement(name="text", required = true)
private String text; }

  使用Marshaller转为xml文本的时候, 结果却是如下:

<root>
<text>&lt;![CDATA[李雷爱韩梅梅]]&gt;</text>
</root>

  这和我们预期的其实有差异, 我们其实想要的是如下的:

<root>
<text><![CDATA[李雷爱韩梅梅]]></text>
</root>

  本质的原因是Jaxb默认会把字符'<', '>'进行转义, 为了解决这个问题, CharacterEscapeHandler就华丽登场了.

import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;

marshaller.setProperty(
"com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler",
new CharacterEscapeHandler() {
@Override
public void escape(char[] ch, int start, int length, boolean isAttVal, Writer writer)
throws IOException {
writer.write(ch, start, length);
}
}
);

  测试结果, 完美地解决问题. 然后随之而来的问题, 稍有些尴尬, 使用maven进行编译打包的时候, 会遇到如下错误:

[ERROR] Compilation failure
[ERROR] 程序包com.sun.xml.internal.bind.marshaller不存在

  Java工程开发, 一般不建议直接调用内部的api(以com.sun开头).

改进方案:

  参考了不少网友的博文, 大致思路都是一样的, 就是借助重载XMLStreamWriter类实现. 更确实的做法是重载writeCharacters方法, 在遇到CData标记(<![CDATA[]]>)包围的文本时, 选择调用writeCData函数, 可用以下代码来大致说明:

public class CDataXMLStreamWriter implements XMLStreamWriter {

    // *) 重载writeCharacters, 遇CDATA标记, 则转而调用writeCData方法
@Override
public void writeCharacters(String text) throws XMLStreamException {
if ( text.startsWith("<![CDATA[") && text.endsWith("]]>") ) {
writeCData(text.substring(9, text.length() - 3));
} else {
writeCharacters(text);
}
}
// *) 演示使用
}

  真实的做法, 不会采用完整的去实现XmlStreamWriter接口的方案, 而是采用代理模式.这边采用动态代理的方法.

private static class CDataHandler implements InvocationHandler {
// *) 单独拦截 writeCharacters(String)方法
private static Method gWriteCharactersMethod = null;
static {
try {
gWriteCharactersMethod = XMLStreamWriter.class
.getDeclaredMethod("writeCharacters", String.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} private XMLStreamWriter writer; public CDataHandler(XMLStreamWriter writer) {
this.writer = writer;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ( gWriteCharactersMethod.equals(method) ) {
String text = (String)args[0];
// *) 遇到CDATA标记时, 则转而调用writeCData方法
if ( text != null && text.startsWith("<![CDATA[") && text.endsWith("]]>") ) {
writer.writeCData(text.substring(9, text.length() - 3));
return null;
}
}
return method.invoke(writer, args);
} }

  具体的Marshaller代码片段如下所示:

public static <T> String mapToXmlWithCData(T obj) {

    try {

        StringWriter writer = new StringWriter();
XMLStreamWriter streamWriter = XMLOutputFactory.newInstance()
.createXMLStreamWriter(writer);
// *) 使用动态代理模式, 对streamWriter功能进行干涉调整
XMLStreamWriter cdataStreamWriter = (XMLStreamWriter) Proxy.newProxyInstance(
streamWriter.getClass().getClassLoader(),
streamWriter.getClass().getInterfaces(),
new CDataHandler(streamWriter)
); JAXBContext jc = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); marshaller.marshal(obj, cdataStreamWriter);
return writer.toString(); } catch (JAXBException e) {
e.printStackTrace();
} catch (XMLStreamException e) {
e.printStackTrace();
}
return null; }

  测试的结果, 完美地解决了CData的问题(功能实现+绕过sun api), 不过这里面还有点小瑕疵, 就是对齐问题, 这段代码没法控制对齐.

对齐改进

  这边需要借助Transformer类实现, 思路是对最终的xml文本进行格式化处理.

// *) 对xml文本进行格式化转化
public static String indentFormat(String xml) {
try {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); StringWriter formattedStringWriter = new StringWriter();
transformer.transform(new StreamSource(new StringReader(xml)),
new StreamResult(formattedStringWriter));
return formattedStringWriter.toString();
} catch (TransformerException e) {
}
return null;
}

  

完整的解决方案

  这边把上述所有的代码完整的贴一遍:

import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource; // *) XmlAdapter类, 修饰类字段, 达到自动添加CDATA标记的目标
public static class CDataAdapter extends XmlAdapter<String, String> {
@Override
public String unmarshal(String v) throws Exception {
return v;
} @Override
public String marshal(String v) throws Exception {
return new StringBuilder("<![CDATA[").append(v).append("]]>")
.toString();
}
} // *) 动态代理
private static class CDataHandler implements InvocationHandler { private static Method gWriteCharactersMethod = null;
static {
try {
gWriteCharactersMethod = XMLStreamWriter.class
.getDeclaredMethod("writeCharacters", String.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
} private XMLStreamWriter writer; public CDataHandler(XMLStreamWriter writer) {
this.writer = writer;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ( gWriteCharactersMethod.equals(method) ) {
String text = (String)args[0];
if ( text != null && text.startsWith("<![CDATA[") && text.endsWith("]]>") ) {
writer.writeCData(text.substring(9, text.length() - 3));
return null;
}
}
return method.invoke(writer, args);
} } // *) 生成xml
public static <T> String mapToXmlWithCData(T obj, boolean formatted) { try { StringWriter writer = new StringWriter();
XMLStreamWriter streamWriter = XMLOutputFactory.newInstance()
.createXMLStreamWriter(writer);
// *) 使用动态代理模式, 对streamWriter功能进行干涉调整
XMLStreamWriter cdataStreamWriter = (XMLStreamWriter) Proxy.newProxyInstance(
streamWriter.getClass().getClassLoader(),
streamWriter.getClass().getInterfaces(),
new CDataHandler(streamWriter)
); JAXBContext jc = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); marshaller.marshal(obj, cdataStreamWriter);
// *) 对齐差异处理
if ( formatted ) {
return indentFormat(writer.toString());
} else {
return writer.toString();
} } catch (JAXBException e) {
e.printStackTrace();
} catch (XMLStreamException e) {
e.printStackTrace();
}
return null; } // *) xml文本对齐
public static String indentFormat(String xml) {
try {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
// *) 打开对齐开关
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
// *) 忽略掉xml声明头信息
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); StringWriter formattedStringWriter = new StringWriter();
transformer.transform(new StreamSource(new StringReader(xml)),
new StreamResult(formattedStringWriter)); return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ formattedStringWriter.toString();
} catch (TransformerException e) {
}
return null;
}

  编写具体的测试案例:

@NoArgsConstructor
@AllArgsConstructor
@XmlRootElement(name="root")
public static class TNode {
@XmlElement(name="key", required = true)
private String key; @XmlJavaTypeAdapter(value=CDataAdapter.class)
@XmlElement(name="text", required = true)
private String text;
} public static void main(String[] args) {
TNode node = new TNode("key", "李雷爱韩梅梅");
String xml = mapToXmlWithCData(node, true);
System.out.println(xml);
}

  测试输出的结果如下:

<?xml version="1.0" encoding="UTF-8"?>
<root>
<key>key</key>
<text><![CDATA[李雷爱韩梅梅]]></text>
</root>

 

总结

  总的来说, 改进的方案规避了sun api的编译限制. 同时能满足之前的功能需求, 值得小小鼓励一下, ^_^.

 

Jaxb如何优雅的处理CData的更多相关文章

  1. jaxb生成cdata块

    1.需要添加一个适配器: import javax.xml.bind.annotation.adapters.XmlAdapter; public class AdapterCDATA extends ...

  2. JAXB XML生成CDATA类型的节点

    试了好久才找到一个解决办法,我是用的JAXB的,如果你们也是用JAXB那么可以直接借鉴此方法,别的方式你们自行测试吧 第一步:新增一个适配器类 package com.message.util; im ...

  3. java注解生成xml和包含CDATA问题

    百度java生成xml,有一大推的文章,主要的生成方式一种使用Dom4J ,还有一种使用Jdk自带注解类! 下面主要整理我注解类的使用,(可以参考这篇文章Dom4J生成xml和包含CDATA问题)和x ...

  4. JAXB注解的使用详解

    前言: 最近一直在做各种接口的对接,接触最多的数据类型就是JSON和XML数据,还有XML中包含JSON的数据,而在Java中对象和XML之间的转换经常用到JAXB注解,抽空在这里总结一下,首先做一下 ...

  5. JAXBContext处理CDATA

    今天做Lucene数据源接口时,遇到一个问题,就是输出xml时将某些数据放在CDATA区输出: 1.依赖的jar包,用maven管理项目的话, <dependency> <group ...

  6. XML学习记录1-复习SAX,DOM和JAXB

    对xml文档的解析常见的有JDK中的sax,dom,jaxb,stax和JAVA类库JDOM和DOM4J,下面先说说前三个. Java中解析XML的工具很多,像JDOM,DOM4J等,但Java标准库 ...

  7. XML CData 处理

    调研了 JAXB.XMLMapper(jackson) 具体方式 实现 优势 JAXB 1. 需要增加 CDATA 的Adaptor 2. 需要增加对非CDATA 的 CharacterEscapeH ...

  8. [手把手教程][JavaWeb]优雅的SpringMvc+Mybatis整合之路

    来源于:http://www.jianshu.com/p/5124eef40bf0 [手把手教程][JavaWeb]优雅的SpringMvc+Mybatis整合之路 手把手教你整合最优雅SSM框架:S ...

  9. [Egret]优雅的写http

    首先,自从使用链式调用的写法后,就一发不可收拾的喜爱上了这种优雅的方式.不管是写架构还是写模块,我都会不自觉的使用这种最优雅的方式.链式写法既减少了代码量,又非常优雅的. 在使用 egret 的htt ...

随机推荐

  1. [luogu P2319] [HNOI2006]超级英雄

    [luogu P2319] [HNOI2006]超级英雄 题目描述 现在电视台有一种节目叫做超级英雄,大概的流程就是每位选手到台上回答主持人的几个问题,然后根据回答问题的多少获得不同数目的奖品或奖金. ...

  2. Vue中 computed 和 methods的区别

    涉及到计算部分的时候,计算属性是基于它们的依赖进行缓存的,如果说值不变,那么它就不会去重新执行,只有当值发生了改变,它才会去重新执行一次,其它时候它都是缓存的.而方法则会反复计算处理.二者之间的差距就 ...

  3. windows7安装教程(vmware)

    这步是正确安装windows的关键,如果不设置那么安装时将不能识别出磁盘,造成安装不成功. 选择No进行自定义修饰,主要是保证C盘大小合适,其他盘可在安装完成之后再调整. 后续安装步骤全自动,完全不用 ...

  4. linux basic test

    Linux 1◆ 提供连接     2◆ connection baidu.com 3◆ vm tools install Reboot    

  5. C#如何通过反射调用类下的方法

    首先模拟一个mvc的项目,创建一个业务类(HomeService),在类下创建3个方法 public class HomeService { /// <summary> /// 无参方法 ...

  6. react router @4 和 vue路由 详解(全)

    react router @4 和 vue路由 本文大纲: 1.vue路由基础和使用 2.react-router @4用法 3.什么是包容性路由?什么是排他性路由? 4.react路由有两个重要的属 ...

  7. DMA-总结

    概念DMA “Direct Memory Access(存储器直接访问).这是指一种高速的数据传输操作,允许在外部设备和存储器之间直接读写数据.整个数据传输操作在一个称为"DMA控制器&qu ...

  8. AdminLTE 文档

    一个基于 bootstrap 的轻量级后台模板,这个前端界面个人感觉很清爽,对于一个大后端的我来说,可以减少较多的时间去承担前端的工作但又必须去独立去完成一个后台系统开发的任务,并且,文档还算比较齐全 ...

  9. MyBatis逆向工程:根据table生成Model、Mapper、Mapper.xml

    逆向工程工具 下载地址:https://download.csdn.net/download/zhutouaizhuwxd/10779140 1.工程导入Eclipse  2.运行MainUI.jav ...

  10. EF-生成迁移版本

    前面讲到可以使用迁移技术让程序自动更新数据库中相关的结构.在我们每次需要新增模型类时,请一定要养成一个好的习惯,使用Add-Migration命令生成迁移版本.这样能恢复被误删除的表. 一.新增迁移版 ...