前言

  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. js浮点数相加、减、乘、除精确计算

    js 浮点数计算时 ,无缘无辜 后边冒出一堆 小数点………… 貌似js本身的问题,类型不定?????? 只能自己写函数处理..  http://blog.csdn.net/w4bobo/article ...

  2. Linux pwn入门教程——CTF比赛

    Linux pwn入门教程(1)——栈溢出基础 from:https://zhuanlan.zhihu.com/p/38985585   0x00 函数的进入与返回 要想理解栈溢出,首先必须理解在汇编 ...

  3. Python学习之路【第二篇】-pyc简介、Python常用的数据类型及其用法和常用运算符

    1.pyc简介 python程序在运行时也有编译过程,编译后会产生.pyc文件.这是一种由python虚拟机执行的二进制文件(字节码),用于保存内存中PyCodeObject,以便加快程序的加载运行. ...

  4. Nginx反向代理配置教程(php-fpm)

    1.安装nginx http://www.cnblogs.com/lsdb/p/6543441.html 2.安装php-fpm yum install -y php-fpm 3.配置Nginx反向代 ...

  5. 生成PDF文档之iText

    iTextSharp.text.Document:这是iText库中最常用的类,它代表了一个pdf实例.如果你需要从零开始生成一个PDF文件,你需要使用这个Document类.首先创建(new)该实例 ...

  6. Lamda 表达式里的Join和GroupJoin的区别, 如何实现SQL的Left Join效果

    例如,可以将产品表与产品类别表相联接,得到产品名称和与其相对应的类别名称 db.Products .Join ( db.Categories, p => p.CategoryID, c => ...

  7. hdu3518

    题解: 后缀数组 枚举长度为k(1<=k<=len/2)的满足要求的子串个数 代码: #include<cstdio> #include<cmath> #inclu ...

  8. 验证码 kaptcha 参数详解

    Constant 描述 默认值 kaptcha.border 图片边框,合法值:yes , no yes kaptcha.border.color 边框颜色,合法值: r,g,b (and optio ...

  9. 各个版本的jee(servlet,jsp)对应的web.xml的模板

    参考链接: https://yutuo.net/archives/7048a006eeb2ac85.html

  10. Python的网络编程--思维导图

    Python的网络编程--思维导图