【问题标题】:Building large MTOM/XOP messages with JAX-WS使用 JAX-WS 构建大型 MTOM/XOP 消息
【发布时间】:2009-12-16 17:44:01
【问题描述】:

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

我能够让它正确构建响应,以便它正确实现 XOP,但我遇到了与内存相关的问题,因为它在发送之前将 整个 响应存储在内存中。此 Web 服务发送的文件可能会变得非常大(例如千兆字节),因此不能将响应存储在内存中。

This Oracle website(以及this one)似乎解决了这个问题,但我就是不明白。我认为他们使用DataHandler 对象来流式传输请求/响应,但我不知道他们如何实例化它。

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

如何在构建响应时发送响应,而不必先将其全部存储在内存中?

提前感谢您的帮助。


更新 12/17: 我将以下属性添加到 WSDL 中保存二进制数据的架构元素。这会导致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();
    //...
}

【问题讨论】:

    标签: java soap jax-ws mtom xop


    【解决方案1】:

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

    在 Apache CXF 中,我们可以更轻松地执行此操作。您可以只拥有一个返回 DataSource 或 DataHandler 的“getter”。您发布的代码中的详细方案对我来说并不熟悉。

    我认为相同的方法适用于 JDK 的 JAX-WS+JAXB。见this

    【讨论】:

    • 我应该把数据源放在哪里?我是否必须向 SEI/SIB“downloadFileSet”方法添加一个参数(我不想这样做,因为 SEI 是由 wsimport 自动生成的)?
    • 感谢您的链接。我在 WSDL 类型部分的适当元素中添加了“xmime:expectedContentTypes”,这导致 wsimport 在 JAX bean 中创建一个 DataHandler 对象。服务器正在创建响应,但我仍然在客户端收到 OutOfMemoryError 异常。我检查了我的临时文件夹,它正确地保存了超过 4MB 的所有文件(如“StreamingAttachmentFeature”对象中指定的那样)......所以它就像将它们写到磁盘并将它们存储在内存中。
    • 现在您已进入 Sun 参考实现的具体行为领域。也许您应该提出一个新问题,有人会提供帮助。或者你可以使用 CXF :-)
    • 呃...我已经走到了这一步,我现在不想切换实现,但也许我必须这样做。
    【解决方案2】:

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

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

    但目前它只是因为错误JAX_WS-936而无法正常工作

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

    【讨论】:

      猜你喜欢
      • 2015-09-26
      • 2010-11-09
      • 2018-04-21
      • 2013-03-05
      • 1970-01-01
      • 1970-01-01
      • 2014-03-26
      • 2016-11-30
      • 2011-11-04
      相关资源
      最近更新 更多