代码之家  ›  专栏  ›  技术社区  ›  Dan Lew

如何将OutputStream通过管道传输到StreamingDataHandler?

  •  8
  • Dan Lew  · 技术社区  · 17 年前

    我在JAX-WS中有一个Java web服务,它从另一个方法返回一个OutputStream。我似乎不知道如何将OutputStream流式传输到返回的DataHandler中,除了创建一个临时文件,对其进行写入,然后将其作为InputStream重新打开。下面是一个例子:

    @MTOM
    @WebService
    class Example {
        @WebMethod
        public @XmlMimeType("application/octet-stream") DataHandler service() {
            // Create a temporary file to write to
            File fTemp = File.createTempFile("my", "tmp");
            OutputStream out = new FileOutputStream(fTemp);
    
            // Method takes an output stream and writes to it
            writeToOut(out);
            out.close();
    
            // Create a data source and data handler based on that temporary file
            DataSource ds = new FileDataSource(fTemp);
            DataHandler dh = new DataHandler(ds);
            return dh;
        }
    }
    

    主要问题是writeToOut()方法返回的数据可能远远大于计算机的内存。这就是为什么该方法首先使用MTOM来流化数据。我似乎不知道如何直接从OutputStream流式传输数据,我需要将数据流式传输给返回的DataHandler(最终是接收StreamingDataHandler的客户端)。

    我尝试过使用PipedInputStream和PipedOutputStream,但它们似乎不是我所需要的,因为在将PipedOutputStream写入之后,需要返回DataHandler。

    有什么想法吗?

    4 回复  |  直到 17 年前
        1
  •  4
  •   Dan Lew    17 年前

    我按照Christian所说的思路找到了答案(创建一个新线程来执行writeToOut()):

    @MTOM
    @WebService
    class Example {
        @WebMethod
        public @XmlMimeType("application/octet-stream") DataHandler service() {
            // Create piped output stream, wrap it in a final array so that the
            // OutputStream doesn't need to be finalized before sending to new Thread.
            PipedOutputStream out = new PipedOutputStream();
            InputStream in = new PipedInputStream(out);
            final Object[] args = { out };
    
            // Create a new thread which writes to out.
            new Thread(
                new Runnable(){
                    public void run() {
                        writeToOut(args);
                        ((OutputStream)args[0]).close();
                    }
                }
            ).start();
    
            // Return the InputStream to the client.
            DataSource ds = new ByteArrayDataSource(in, "application/octet-stream");
            DataHandler dh = new DataHandler(ds);
            return dh;
        }
    }
    

    这有点复杂,因为 final 变量,但据我所知,这是正确的。当线程启动时,它会在第一次尝试调用时阻塞 out.write() ; 同时,输入流返回给客户机,客户机通过读取数据来解除写操作。(我以前的解决方案实现的问题是,我没有正确地关闭流,因此出现了错误。)

        2
  •  3
  •   Christian    17 年前

    很抱歉,我只是为C#而不是java做的,但是我认为您的方法应该启动一个线程来运行parralel中的“writeToOut(out);”。您需要创建一个特殊的流,并将其传递给新线程,该线程将该流写入。启动线程后,将该流对象返回给调用者。

    如果只有一个方法写入流并随后返回,而另一个方法使用流并随后返回,那么就没有其他方法了。

    当然,棘手的部分是获得这样一个多线程安全流:如果内部缓冲区太满,它将阻止每一方。

    不知道Java管道流是否适用于此。

        3
  •  1
  •   Mihai Toader    17 年前

    包装器模式?:-)。

    定制javax.activation.DataSource实现(只有4种方法)能够做到这一点吗?

    return new DataHandler(new DataSource() { 
      // implement getOutputStream to return the stream used inside writeToOut() 
      ... 
    });   
    

    我没有可用的IDE来测试这个,所以我只是做一个建议。我还需要写出总体布局:-)。

        4
  •  0
  •   Yuriy N.    10 年前

    在我的应用程序中,我使用InputStreamDataSource实现,它将InputStream作为构造函数参数,而不是FileDataSource中的文件。到目前为止,它是有效的。

    public class InputStreamDataSource implements DataSource {
    
    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    private final String name;
    
    public InputStreamDataSource(InputStream inputStream, String name) {
        this.name = name;
        try {
            int nRead;
            byte[] data = new byte[16384];
            while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
    
            buffer.flush();
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    
    }
    
    @Override
    public String getContentType() {
        return new MimetypesFileTypeMap().getContentType(name);
    }
    
    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(buffer.toByteArray());
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new IOException("Read-only data");
    }
    

    }