使用JAX-WS构建大型MTOM / XOP消息

我有一个关于将MTOM / XOP与JAX-WS一起使用的问题。 我正在编写一个发送大量二进制数据的Web服务。 客户端请求许多文件,服务器返回响应中的文件。

我能够正确地构建响应,以便正确实现XOP,但我遇到了与内存相关的问题,因为它在发送之前将整个响应存储在内存中。 此Web服务发送的文件可能会变得非常大(例如,giga-bytes large),因此将响应存储在内存中不是一种选择。

这个Oracle网站 (以及这个网站 )似乎解决了这个问题,但我只是不理解它。 我认为他们使用DataHandler对象来传输请求/响应,但我无法弄清楚它们如何实例化它。

我正在使用wsimport从现有的WSDL生成我的JAX-WS类文件。 我正在使用JAX-WS RI 2.1.6和Java 6。

我如何在建立响应时发送响应,而不必先将所有响应存储在内存中?

在此先感谢您的帮助。


更新12月17日:我将以下属性添加到保存二进制数据的WSDL中的schema元素。 这会导致wsimportDataHandler对象添加到JAXB类。 然后可以将FileDataHandler添加到响应中,而不是添加文件的全部内容,从而允许服务器流式传输每个文件的内容,而不是将它们全部保存在内存中:

 xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmime:expectedContentTypes="application/octet-stream" 

因此,服务器现在正确地构建响应,并且客户端在收到请求时正确地将每个文件保存到磁盘。 但是,客户端仍然会出于某种原因将整个响应读入内存。


服务器代码(SIB):

 @MTOM @StreamingAttachment(parseEagerly = true, memoryThreshold = 4000000L) @WebService(...) public class DownloadFilesPortTypeImpl implements DownloadFilesPortType { @Override public FileSetResponseType downloadFileSet(FileSetRequestType body) { FileSetResponseType response = new FileSetResponseType(); for (FileRequest freq : body.getFileRequest()){ try{ //find the file on disk File file = findFile(freq.getFileId()); //read the file data into memory byte[] fileData; { FileInputStream in = new FileInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream(); byte buf[] = new byte[8192]; int read; while ((read = in.read(buf)) != -1){ out.write(buf, 0, read); } in.close(); out.close(); fileData = out.toByteArray(); } //add the file to the response FileResponse fresp = new FileResponse(); fresp.setFileId(freq.getFileId()); fresp.setData(fileData); //<-- type "xs:base64Binary" response.getFileResponse().add(fresp); } catch (IOException e){ } } return response; } } 

客户端代码:

 DownloadFilesService service = new DownloadFilesService(); MTOMFeature mtomFeature = new MTOMFeature(); StreamingAttachmentFeature stf = new StreamingAttachmentFeature(null, true, 4000000L); DownloadFilesPortType port = service.getDownloadFilesPortSoap12(mtomFeature, stf); FileSetRequestType request = new FileSetRequestType(); FileRequest freq = new FileRequest(); freq.setFileId("1234"); request.getFileRequest().add(freq); freq = new FileRequest(); freq.setFileId("9876"); request.getFileRequest().add(freq); //... FileSetResponseType response = port.downloadFileSet(request); //reads entire response into memory for (FileResponse fres : response.getFileResponse()){ byte[] data = fres.getFileData(); //... } 

您创建自己的类来实现DataSource并构造传递它的DataHandler。它甚至可以是匿名的。

在Apache CXF中,我们可以更轻松地完成此任务。 你可以拥有一个返回DataSource或DataHandler的’getter’。 您发布的代码中精心设计的方案对我来说并不熟悉。

我认为相同的方法适用于JDK的JAX-WS + JAXB。 看到这个 。

由于Java 6中包含JAX-WS RI(Metro或其中的一部分),客户端代码必须设置HTTP分块大小,以通过DataHandler InputStream启用大型MTOM附件的流式传输。

 ((BindingProvider)port).getRequestContext() .put(JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE, 8192); 

但由于JAX_WS-936的错误,目前它不起作用

因此,即使使用正确的分块和DataHandler,附件也会在服务调用时立即完全加载到内存中。 我检查了网络流量:在读取DataHandler输入流之前完全内容传输,以及Java堆内存使用情况:它已经增加到包含至少三个附件副本。