最新的 Open JDK 8 JAXB 库无法解组具有包含换行符的属性的对象

Posted

技术标签:

【中文标题】最新的 Open JDK 8 JAXB 库无法解组具有包含换行符的属性的对象【英文标题】:Latest Open JDK 8 JAXB library fails to unmarshal objects with properties that contain new line characters 【发布时间】:2018-07-14 05:01:52 【问题描述】:

我在 Ubuntu 16.04 上使用 Java。最近我升级到使用 oracle-java8-installer 包(包版本 8u161-1~webupd8~0)安装的 Open JDK java 版本“1.8.0_161”。自从进行此升级以来,我在对 Java 对象进行 JAXB 编组时遇到了新异常。

具体来说,当尝试使用 JAXB 将 Java 对象编组为 XML 时,如果 Java 对象具有包含任何换行符 ("\n") 字符的 String 属性并且该 String 属性被序列化为元素,则会出现以下异常XML 中的内容。 (顺便说一句,如果 String 属性被序列化为属性内容,则 String 的值中的任何换行符都会转换为空格字符,不会触发异常。)

似乎正在发生的是

com.sun.xml.internal.bind.v2.runtime.output.XMLStreamWriterOutput$NewLineEscapeHandler.escape

将Java 对象的String 属性中的换行符转换为实体引用
。然后将此实体引用写入 XML 输出流,但在验证实体引用名称时,将引发异常,因为 #xa 未被识别为有效的实体引用名称。

这是预期的行为吗?如果是这样,我应该怎么做才能在 Java 对象的序列化中保留换行符?如果没有,我应该怎么做才能解决这个问题?

堆栈跟踪的相关部分是:

... Caused by: javax.xml.stream.XMLStreamException: Invalid name start character '#' (code 35) (name "#xa")
at com.fasterxml.aalto.out.XmlWriter.throwOutputError(XmlWriter.java:472)
at com.fasterxml.aalto.out.XmlWriter.reportNwfName(XmlWriter.java:383)
at com.fasterxml.aalto.out.ByteXmlWriter.verifyNameComponent(ByteXmlWriter.java:235)
at com.fasterxml.aalto.out.ByteXmlWriter.constructName(ByteXmlWriter.java:181)
at com.fasterxml.aalto.out.WNameTable.findSymbol(WNameTable.java:324)
at com.fasterxml.aalto.out.StreamWriterBase.writeEntityRef(StreamWriterBase.java:615)
at net.galexy.fieldguide.jaxb.CustomXMLStreamWriter.writeEntityRef(CustomXMLStreamWriter.java:198)
at com.sun.xml.internal.bind.v2.runtime.output.XMLStreamWriterOutput$XmlStreamOutWriterAdapter.writeEntityRef(XMLStreamWriterOutput.java:277)
at com.sun.xml.internal.bind.v2.runtime.output.XMLStreamWriterOutput$NewLineEscapeHandler.escape(XMLStreamWriterOutput.java:242)
... 60 more

例如,如果我解组以下 XML:

<?xml version='1.0' encoding='UTF-8'?>
<description>
   <note>The text of the note</note>
</description>

然后尝试将其编组回 XML,然后不会引发异常。

但是,如果注释内容中间有新行:

<?xml version='1.0' encoding='UTF-8'?>
<description>
   <note>The text of
         the note</note>
</description>

然后抛出异常。

正在使用的 JAXB 上下文是 com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl

正在使用的 JAXB 编组器是 com.sun.xml.internal.bind.v2.runtime.MarshallerImpl

在寻找有关更改的更多信息时,我发现了以下错误报告,表明其他人在此版本的 JAXB 中遇到了相同的更改:

JDK-8196491 Newlines in JAXB string values of SOAP-requests are escaped to "&#xa;"

this stack overflow question 的答案表明我可以通过让我的编组器使用 com.sun.xml.bind.marshaller.CharacterEscapeHandler 的自定义实现来恢复对字符转义的控制。

这让我感到困惑,因为javax.xml.bind.Marshaller 似乎没有声明静态属性名称com.sun.xml.bind.marshaller.CharacterEscapeHandler,而它确实声明了其他属性名称,例如Marshaller.JAXB_FORMATTED_OUTPUT,等于"jaxb.formatted.output

即使我可以指示编组器使用我的自定义字符转义处理程序,我也不能完全确定我应该在该转义处理程序中做什么。是否有适当的基本转义处理程序,我可以覆盖它以继承所有标准转义处理,确保我进行干预以停止换行符的转义?

我也试过 Oracle Java 9(包版本 9.0.4-1~webupd8~0),那个版本的 Java 也有同样的问题。

我也尝试过 Oracle Java 8 (1.8.0_162) 的下一个版本,该版本也有同样的问题。

从 Oracle 网站 (1.8.0_152) 下载较旧版本的 Java 可以解决问题,但不是解决问题的令人满意的方法。

【问题讨论】:

【参考方案1】:

在我的例子中,我使用 JAXB 将一些对象转换为 XML,并通过 StAX/WoodStox 将它们序列化为一个文件。我已经通过过滤正在序列化的 XML 来解决问题。详细来说,方法是这样的:

    定义一个自定义的StreamWriter2Delegate,覆盖writeEntityRef(),这样,当这个方法接收到错误的实体代码(#xd#xa)时,它会调用它的委托来实际写回原始字符(即, \n\r),实际上不需要转义:

    @Override
    public void writeEntityRef ( String eref ) throws XMLStreamException
    
        if ( eref == null || !eref.startsWith ( "#x" ) ) 
            super.writeEntityRef ( eref );
            return;
        
        String hex = eref.substring ( 2 );
        for ( char c: new char[]  '\r', '\n'  )
            if ( Integer.toHexString ( c ).equals ( hex ) ) 
                this.writeCharacters ( Character.toString ( c ) );
                return;
        
        super.writeEntityRef ( eref );
    
    

这等效于(除了一些开销)fix they've already filed 解决这个问题,它应该在 JDK8u192 中可用(并且应该已经在 J​​DK 9/10 中)。

    用上面的过滤器包裹你的XMLStreamWriter2,例如:

    FileOutputStream fout = new FileOutputStream ( "test.xml" );
    WstxOutputFactory wsof = (WstxOutputFactory) WstxOutputFactory.newInstance();
    XMLStreamWriter2 xmlOut = (XMLStreamWriter2) wsof.createXMLStreamWriter ( fout, CharsetNames.CS_UTF8 );
    xmlOut = new NewLineFixWriterFilter ( xmlOut );
    // Now write into xmlOut, directly or via JAXB
    

完整/生产代码是here。将相同的方法应用于类似的管道应该不难(一般来说,出现问题是因为com.sun.xml.internal.bind.v2.runtime.output.XMLStreamWriterOutput以错误的方式转义\n\r,所以诀窍是从上层劫持这个错误的编码)。

【讨论】:

【参考方案2】:

杰夫·S,

我试图对现有帖子发表评论,但我很快发现您需要拥有“50 个声誉”,而我没有。

当我们迁移到 JDK 1.8.0_161 和 1.8.0_162 时,我似乎遇到了类似的问题,我们的一些 SOAP 服务开始抛出以下异常

Feb 28, 2018 8:34:12 AM com.sun.xml.internal.messaging.saaj.soap.SOAPDocumentImpl createEntityReference
SEVERE: SAAJ0543: Entity References are not allowed in SOAP documents
SEVERE: java.lang.UnsupportedOperationException: Entity References are not allowed in SOAP documents
javax.xml.ws.WebServiceException: java.lang.UnsupportedOperationException: Entity References are not allowed in SOAP documents
    at com.sun.xml.internal.ws.handler.ClientSOAPHandlerTube.callHandlersOnRequest(ClientSOAPHandlerTube.java:135)
    at com.sun.xml.internal.ws.handler.HandlerTube.processRequest(HandlerTube.java:112)
    at com.sun.xml.internal.ws.api.pipe.Fiber.__doRun(Fiber.java:1121)
    at com.sun.xml.internal.ws.api.pipe.Fiber._doRun(Fiber.java:1035)
    at com.sun.xml.internal.ws.api.pipe.Fiber.doRun(Fiber.java:1004)
    at com.sun.xml.internal.ws.api.pipe.Fiber.runSync(Fiber.java:862)
    at com.sun.xml.internal.ws.client.Stub.process(Stub.java:448)
    at com.sun.xml.internal.ws.client.sei.SEIStub.doProcess(SEIStub.java:178)
    at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:93)
    at com.sun.xml.internal.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:77)
    at com.sun.xml.internal.ws.client.sei.SEIStub.invoke(SEIStub.java:147)
    at com.sun.proxy.$Proxy38.getUserProfile(Unknown Source)

如上述问题和其他线程所示:

https://bugs.openjdk.java.net/browse/JDK-8196491 https://bugs.java.com/view_bug.do?bug_id=8196491

它与有效负载中的换行符有关。例如,我们的一些有效负载包括具有导致问题的新行的 XML 字符串。但是,如果在调用服务之前删除了换行符,那么它就可以工作。见下文:

失败

<?xml version="1.0" encoding="UTF-8"?>
<user>
<userId>XXXX</userId>
<name>XXXXXX, XXXXXX</name>
<phone>(xxx)xxx-xxxx</phone>
<title><![CDATA[MY TITLE]]></title>
<mail>xxx@xxxx.com</mail>
</user>

作品

<?xml version="1.0" encoding="UTF-8"?><user><userId>XXXX</userId><name>XXXXXX, XXXXXX</name><phone>(xxx)xxx-xxxx</phone><title><![CDATA[MY TITLE]]></title><mail>xxx@xxxx.com</mail></user>

您或其他人是否知道除了从“新行”中剥离有效负载之外是否有其他解决方法,这是否被认为是最新 Oracle JDK 中的一个错误,是否有任何纠正该行为的计划。

谢谢

最大

【讨论】:

嗨 Max,我的解决方法是手动安装 Java JDK 的早期版本。在这个阶段我不清楚这是否被认为是一个错误。 嗨 Geoff,感谢您的回复,我们通过降级做了同样的事情我只是不确定安全性允许我们多长时间。我希望甲骨文的某个人会跳到他们的线程上提供见解,看看这是他们计划修复的错误还是让它成为现实。谢谢最大 哪个版本?能给个链接吗?

以上是关于最新的 Open JDK 8 JAXB 库无法解组具有包含换行符的属性的对象的主要内容,如果未能解决你的问题,请参考以下文章

如何调试 JAXB 解组?

JAXB 解组中的忽略和符号 (Java 1.8)

JAXB 解组@XmlAnyElement

使用 DTD 文件导致的 JAXB 解组 XML 时出错

解组 JAXB 编组列表失败并出现 NullPointerException

解组非根元素时的 JAXB 模式验证