为什么CXF/JAXB在编组到SOAP消息之前将整个InputStream读取到内存中

为什么CXF/JAXB在编组到SOAP消息之前将整个InputStream读取到内存中,jaxb,cxf,jax-ws,marshalling,jaxb2,Jaxb,Cxf,Jax Ws,Marshalling,Jaxb2,信息-示例代码 我已经为您设置了示例代码(SSCCE),以帮助跟踪问题: 问题 我正在与第三方JAX-WS服务集成,因此我不能更改WSDL 第三方Web服务希望Base64编码的字节对它们执行一些操作-他们希望客户端在SOAP消息中发送整个字节。 他们不想更改为MTOM/XOP,所以我只能满足当前的需求 我决定使用CXF轻松设置示例客户机,对于小文件来说,它工作正常 但当我尝试发送大数据(即200MB)时,CXF/JAXB抛出了一个异常: Exception in thread "main"

信息-示例代码

我已经为您设置了示例代码(SSCCE),以帮助跟踪问题:

问题

我正在与第三方JAX-WS服务集成,因此我不能更改WSDL

第三方Web服务希望Base64编码的字节对它们执行一些操作-他们希望客户端在SOAP消息中发送整个字节。 他们不想更改为MTOM/XOP,所以我只能满足当前的需求

我决定使用CXF轻松设置示例客户机,对于小文件来说,它工作正常

但当我尝试发送大数据(即200MB)时,CXF/JAXB抛出了一个异常:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.sun.xml.bind.v2.util.ByteArrayOutputStreamEx.readFrom(ByteArrayOutputStreamEx.java:75)
at com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data.get(Base64Data.java:196)
at com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data.writeTo(Base64Data.java:312)
at com.sun.xml.bind.v2.runtime.output.UTF8XmlOutput.text(UTF8XmlOutput.java:312)
at com.sun.xml.bind.v2.runtime.XMLSerializer.leafElement(XMLSerializer.java:356)
at com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$PcdataImpl.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:191)
at com.sun.xml.bind.v2.runtime.MimeTypedTransducer.writeLeafElement(MimeTypedTransducer.java:96)
at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:254)
at com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:130)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:360)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:696)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:155)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:130)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:332)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:339)
at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:75)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:251)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:95)
at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder.java:617)
at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:241)
at org.apache.cxf.jaxb.io.DataWriterImpl.write(DataWriterImpl.java:237)
at org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor.writeParts(AbstractOutDatabindingInterceptor.java:117)
at org.apache.cxf.wsdl.interceptors.BareOutInterceptor.handleMessage(BareOutInterceptor.java:68)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:514)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:423)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:324)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:277)
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:139)
我的发现

我跟踪了这个bug,它基于xsd类型“base64Binary”,即

com.sun.xml.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl

决定

com.sun.xml.bind.v2.runtime.unmarshaller.Base64Data

应处理来自的数据编组

javax.activation.DataHandler

在封送过程中,试图读取底层InputStream中的所有数据,这会导致OOME异常

问题

CXF在将Java对象编组为SOAP消息时使用JAXB—编组InputStream时,整个输入流在转换为Base64二进制之前被读取到内存中

因此,我希望以块的形式将(“流”)数据从客户端发送到服务器(因为marshaller中的OutputSteam是直接包装的HttpURLConnection),这样我的客户端就可以处理发送任意数量的数据

特别是当很多线程都在使用我的客户端时,流式处理是非常理想的

我没有很好的JAX-WS/CXF/JAXB知识,这就是问题所在

我发现的唯一有用的材料是:

问题

  • 为什么CXF/JAXB将整个InputStream加载到内存中?DataHandler不是为了阻止这种实现吗

  • 您知道如何将JAXB行为更改为InputStream吗

  • 您知道不同的封送器,它们可以处理如此大的数据封送吗

  • 作为最后的手段,也许您有一些材料的链接,如何创建自定义封送器,将数据直接流到服务器


  • 您不需要任何定制封送器,也不需要更改JAXB行为来实现您所需要的—这里是您的朋友

    回答您的第一个问题:JAXB需要将所有数据保存在内存中,因为它必须解析引用

    我知道您不能更改WSDL引用等,但是您的项目中仍然有您的客户机的WSDL,以便生成客户机类,不是吗?因此,您可以做的是(我没有用第三方的WSDL测试这个,但可能值得一试)将
    xmime:expectedContentTypes=“application/octet stream”
    添加到返回Base64编码数据的响应XSD元素中。例如:

    <xsd:element name="generateBigDataResponse">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="result"
                             type="xsd:base64Binary"
                             minOccurs="0"
                             maxOccurs="1"
                             xmime:expectedContentTypes="application/octet-stream"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    
    由于
    xmime
    ,您可以将
    DataHandler
    作为响应类型。我建议您使用,但请确保使用不同的线程:

    管道输出流可以连接到管道输入流以 创建一个通信管道。管道输出流是发送流 管的末端。通常,数据会写入Pipedoutput流 对象,并从连接的 PipedInputStream由其他线程执行。试图同时使用这两个对象 不建议从单个线程执行,因为它可能会使线程死锁。 如果线程正在读取数据字节,则称管道已断开 来自已连接管道的输入流不再处于活动状态

    然后将它和哪个实例连接到构造函数中,然后将其传递到
    DataHandler
    并返回
    DataHandler
    的实例。这样,您的文件将被分块写入,您将不会得到异常,更多-客户端将永远不会得到超时

    希望这有帮助。

    1。你看过我在问题开始时提到的示例项目()了吗?2.在那个例子中,我已经按照你的建议做了,但OOME仍然存在——这是因为JAXB的实现忽略了DataHandler的使用,仍然将所有内容缓存到内存中()**3.**你还有其他想法如何解决这个问题吗?
    @Override
    public DataHandler generateBigData() {
        try {
            final PipedOutputStream pipedOutputStream = new PipedOutputStream();
            PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);
            InputStreamDataSource dataSource = new InputStreamDataSource(pipedInputStream, "application/octet-stream");
    
            executor.execute(new Runnable() {
    
                @Override
                public void run() {
                    //write your stuff here into pipedOutputStream
                }
            });
    
            return new DataHandler(dataSource);
        } catch (IOException e) {
            //handle exception if any
        }
    }