【问题标题】:stream a large String into JAXB将大字符串流式传输到 JAXB
【发布时间】:2013-11-18 17:25:15
【问题描述】:

我的 JAXB 层次结构中有一个域对象,它必须表示为逗号分隔值文本。不幸的是,显式构建 CSV String 的成本非常高,所以这不是一种选择。

我创建了一个自定义的@XmlJavaTypeAdapter,它返回了一个DataHandler(根据supported data types),但它总是在BASE64中写出数据......但我有一个遗留的API来保存它,它需要ASCII字符串.更改 DataHandler 的 MIME 不会更改编码,但会影响 XSD 对其中包含的对象的定义。

有什么方法可以设置 DataHandler(或任何其他受支持的 Java 类型)以从流输入返回未编码的 String

我也考虑过返回一个Object(实际上是一个CharacterData),但这需要实现public String getData()...要求我明确地构造我正在尝试流式传输的String

【问题讨论】:

    标签: java xml jaxb


    【解决方案1】:

    如果没有人提出与DataHanler 相关的解决方案...以下只是不涉及DataHandler 的“变通办法”的另一种想法。它需要访问 marshaller。

    • 修改您的 XML 类型适配器,使其不返回内容,而是返回一种短地址以获取流数据(例如文件名)。

    • 定义一个 XMLStreamWriter 包装器,如下所示:JAXB marshalling XMPP stanzas。覆盖writeStartElementwriteCharacters 以拦截CSV 元素的startElement 调用和紧随其后的writeCharacters

    • 传递给writeCharacters 的特定调用的数据将是获取流数据的地址。将其分块流式传输到包装的 XMLStreamWriter 的 writeCharacters。

    【讨论】:

    • 这看起来可行!我会试一试,如果有的话(可能一周)回复。
    【解决方案2】:

    我不太明白为什么显式构造 CSV 字符串(使用 StringBuilder)会比使用 JAXB 内置函数更昂贵。

    如果性能是您的限制因素,那么我认为您应该考虑创建自定义序列化程序(例如基于 StringBuilder)和 SAX 处理程序来解析 XML。

    如果您有幸更改协议,那么您可能想查看Grizzly frameworkAvroGoogle ProtoBuf - 它们需要更多维护,但如果您追求性能,那么这些应该更快。

    与往常一样,您应该使用这两种方法进行 A/B 性能测试,然后再将任何东西固定下来;)

    回到原来的话题,下面是一个关于如何使用自定义适配器的例子:

    import static org.junit.Assert.assertEquals;
    
    import java.io.StringWriter;
    
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
    
    import org.junit.Test;
    
    public class Example
    {
        public String serialize( DataObject d ) throws JAXBException {
            StringWriter buffer = new StringWriter();
            JAXBContext.newInstance(DataObject.class).createMarshaller().marshal(d, buffer);
            return buffer.toString();
        }
    
        @Test
        public void testSerialize( ) throws JAXBException {
            String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><dataObject>"
                              + "<FirstField>field1 content with special characters &amp;&lt;&gt;'\"</FirstField>"
                              + "<Second>&lt;!CDATA[[ &lt;!-- now we're just nasty --&gt; ]]&gt;</Second>"
                              + "<Custom>a,b,c</Custom></dataObject>";
    
            assertEquals(expected, serialize(new DataObject()).replaceAll("(\r)?\n(\r)?", "\n"));
        }
    }
    
    @XmlRootElement
    @XmlAccessorType( XmlAccessType.FIELD )
    class DataObject
    {
        @XmlElement( name = "FirstField" )
        private final String field1 = "field1 content with special characters &<>'\"";
    
        @XmlElement( name = "Second" )
        private final String field2 = "<!CDATA[[ <!-- now we're just nasty --> ]]>";
    
        @XmlElement( name = "Custom" )
        @XmlJavaTypeAdapter( value = CustomAdapter.class )
        // you can move this over the type
        private final CustomType type = new CustomType("a", "b", "c");
    }
    
    @XmlAccessorType( XmlAccessType.FIELD )
    class CustomType
    {
        private final String a;
        private final String b;
        private final String c;
    
        public CustomType( String a, String b, String c ) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    
        public String getA( ) {
            return a;
        }
    
        public String getB( ) {
            return b;
        }
    
        public String getC( ) {
            return c;
        }
    }
    
    class CustomAdapter extends XmlAdapter<String, CustomType>
    {
        @Override
        public String marshal( CustomType v ) throws Exception {
            return String.format("%s,%s,%s", v.getA(), v.getB(), v.getC());
        }
    
        @Override
        /** Please don't use this in PROD :> */
        public CustomType unmarshal( String v ) throws Exception {
            String[] split = v.split(",");
            return new CustomType(split[ 0 ], split[ 1 ], split[ 2 ]);
        }
    }
    

    这应该会让你继续前进,除非我完全误解了你的问题。

    【讨论】:

    • buffer.toString() => OutOfMemoryError。简而言之,这就是问题所在 :-) (实际上,我们并没有 OOM,但想象一下同时处理大量这些请求,并想象一下 GC 在这种负载下如何应对。我们已经对其进行了分析,这是一个杀手,流媒体是解决方案。)
    • 哦,这就是问题所在。我想您可以尝试在编组时使用输出流而不是字符串编写器,但这也可能会爆炸,具体取决于实现细节。另一件事是切换到完全流式 API - 这里有一些示例java.dzone.com/articles/xml-unmarshalling-benchmark(“JAXB + STax”部分),但如果 CSV 字符串本身就是问题,那么恐怕你遇到了一个极端情况。无论如何,如果我发现一些有趣的东西,我会在这里发布。
    • 确切地说,我正在尝试将可流式对象传递给 JAXB(即从较小的对象动态构建 CSV)。我遇到的问题是 JAXB 中唯一可流式传输的对象流入 base64 并且我需要 ASCII。上面提供自定义拦截 XMLStreamWriter 的建议听起来不错,但很想听听您可能拥有的任何其他方法。
    猜你喜欢
    • 2019-03-20
    • 2020-10-02
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 2013-02-15
    相关资源
    最近更新 更多