为什么CXF/JAXB在编组到SOAP消息之前将整个InputStream读取到内存中
信息-示例代码 我已经为您设置了示例代码(SSCCE),以帮助跟踪问题: 问题 我正在与第三方JAX-WS服务集成,因此我不能更改WSDL 第三方Web服务希望Base64编码的字节对它们执行一些操作-他们希望客户端在SOAP消息中发送整个字节。 他们不想更改为MTOM/XOP,所以我只能满足当前的需求 我决定使用CXF轻松设置示例客户机,对于小文件来说,它工作正常 但当我尝试发送大数据(即200MB)时,CXF/JAXB抛出了一个异常:为什么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"
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知识,这就是问题所在
我发现的唯一有用的材料是:
问题
您不需要任何定制封送器,也不需要更改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
}
}