【问题标题】:Validation using JAXB and Stax to marshal XML document使用 JAXB 和 Stax 来编组 XML 文档的验证
【发布时间】:2011-01-29 02:14:28
【问题描述】:

我创建了一个 XML 模式 (foo.xsd) 并使用 xjc 为 JAXB 创建绑定类。假设根元素是 collection,我正在编写 N 个 document 对象,它们是复杂类型。

因为我打算写出大型 XML 文件,所以我使用 Stax 写出 collection 根元素,并使用 JAXB 使用 Marshaller.marshal(JAXBElement, XMLEventWriter) 编组文档子树。这是jaxb's unofficial user's guide推荐的方法。

我的问题是,如何在编组时验证 XML?如果我将模式绑定到 JAXB 编组器(使用 Marshaller.setSchema()),我会收到验证错误,因为我只是编组一个子树(它抱怨它没有看到 collection 根元素“)。我想我真正想要什么要做的是将架构绑定到Stax XMLEventWriter 或类似的东西。

任何关于这种整体方法的 cmets 都会有所帮助。基本上我希望能够使用JAXB 来编组和解组大型 XML 文档而不会耗尽内存,所以如果有更好的方法可以让我知道。

【问题讨论】:

    标签: java xml xsd jaxb stax


    【解决方案1】:

    只有在 Marshaller 调用 Iterator.next() 时,您才能使根集合变得惰性并实例化项目。然后对marshal() 的一次调用将产生一个巨大的经过验证的XML。您不会耗尽内存,因为已经序列化的 bean 会被 GC 收集。

    另外,如果需要有条件地跳过 null 作为集合元素返回也可以。不会有 NPE。

    即使在大型 XML 上,XML 模式验证器本身似乎消耗的内存也很少。

    查看 JAXB 的 ArrayElementProperty.serializeListBody()

    import java.io.IOException;
    import java.io.StringReader;
    import java.io.StringWriter;
    import java.io.Writer;
    import java.util.AbstractList;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.xml.XMLConstants;
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBElement;
    import javax.xml.bind.Marshaller;
    import javax.xml.bind.SchemaOutputResolver;
    import javax.xml.bind.annotation.XmlAccessType;
    import javax.xml.bind.annotation.XmlAccessorType;
    import javax.xml.bind.annotation.XmlAnyElement;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    import javax.xml.namespace.QName;
    import javax.xml.transform.Result;
    import javax.xml.transform.stream.StreamResult;
    import javax.xml.transform.stream.StreamSource;
    import javax.xml.validation.Schema;
    import javax.xml.validation.SchemaFactory;
    
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlRootElement(name = "TestHuge")
    public class TestHuge {
    
        static final boolean MISPLACE_HEADER = true;
    
        private static final int LIST_SIZE = 20000;
    
        static final String HEADER = "Header";
    
        static final String DATA = "Data";
    
        @XmlElement(name = HEADER)
        String header;
    
        @XmlElement(name = DATA)
        List<String> data;
    
        @XmlAnyElement
        List<Object> content;
    
        public static void main(final String[] args) throws Exception {
    
            final JAXBContext jaxbContext = JAXBContext.newInstance(TestHuge.class);
    
            final Schema schema = genSchema(jaxbContext);
    
            final Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            marshaller.setSchema(schema);
    
            final TestHuge instance = new TestHuge();
    
            instance.content = new AbstractList<Object>() {
    
                @Override
                public Object get(final int index) {
                    return instance.createChild(index);
                }
    
                @Override
                public int size() {
                    return LIST_SIZE;
                }
            };
    
            // throws MarshalException ... Invalid content was found starting with element 'Header'
            marshaller.marshal(instance, new Writer() {
    
                @Override
                public void write(final char[] cbuf, final int off, final int len) throws IOException {}
    
                @Override
                public void write(final int c) throws IOException {}
    
                @Override
                public void flush() throws IOException {}
    
                @Override
                public void close() throws IOException {}
            });
    
        }
    
        private JAXBElement<String> createChild(final int index) {
            if (index % 1000 == 0) {
                System.out.println("serialized so far: " + index);
            }
            final String tag = index == getHeaderIndex(content) ? HEADER : DATA;
    
            final String bigStr = new String(new char[1000000]);
            return new JAXBElement<String>(new QName(tag), String.class, bigStr);
        }
    
        private static int getHeaderIndex(final List<?> list) {
            return MISPLACE_HEADER ? list.size() - 1 : 0;
        }
    
        private static Schema genSchema(final JAXBContext jc) throws Exception {
            final List<StringWriter> outs = new ArrayList<>();
            jc.generateSchema(new SchemaOutputResolver() {
    
                @Override
                public Result createOutput(final String namespaceUri, final String suggestedFileName)
                                                                                                      throws IOException {
                    final StringWriter out = new StringWriter();
                    outs.add(out);
                    final StreamResult streamResult = new StreamResult(out);
                    streamResult.setSystemId("");
                    return streamResult;
                }
            });
            final StreamSource[] sources = new StreamSource[outs.size()];
            for (int i = 0; i < outs.size(); i++) {
                final StringWriter out = outs.get(i);
                sources[i] = new StreamSource(new StringReader(out.toString()));
            }
            final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            final Schema schema = sf.newSchema(sources);
            return schema;
        }
    }
    

    【讨论】:

      【解决方案2】:

      一些 Stax 实现似乎能够验证输出。请参阅以下类似问题的答案:

      Using Stax2 with Woodstox

      【讨论】:

        猜你喜欢
        • 2014-10-27
        • 2011-08-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多