Как передать OutputStream в StreamingDataHandler?
Вопрос
У меня есть веб-служба Java в JAX-WS, которая возвращает 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, но, похоже, это не совсем то, что мне нужно, потому что DataHandler необходимо будет вернуть после записи в PipedOutputStream.
Есть идеи?
Решение
Я нашел ответ в том же духе, о котором говорил Кристиан (создание нового потока для выполнения 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()
;в то же время входной поток возвращается клиенту, который разблокирует запись путем чтения данных.(Проблема моих предыдущих реализаций этого решения заключалась в том, что я неправильно закрывал поток и, следовательно, сталкивался с ошибками.)
Другие советы
Извините, я сделал это только для C#, а не Java, но я думаю, что ваш метод должен запустить ветку для запуска «writetoout (out)»; в Парралиле.Вам нужно создать специальный поток и передать его в новый поток, который передает этот поток в writeToOut.После запуска потока вы возвращаете этот объект потока вызывающему объекту.
Если у вас есть только метод, который записывает в поток и потом возвращает результат, и другой метод, который потребляет поток и потом возвращает результат, другого пути нет.
Конечно, сложнее всего получить такой многопоточный безопасный поток:Он будет блокировать каждую сторону, если внутренний буфер слишком полон.
Не знаю, подойдет ли для этого Java-pipe-stream.
Образец обертки?:-).
Пользовательская реализация javax.activation.DataSource (всего 4 метода), чтобы иметь возможность это сделать?
return new DataHandler(new DataSource() {
// implement getOutputStream to return the stream used inside writeToOut()
...
});
У меня нет IDE, чтобы проверить это, поэтому я просто предлагаю.Мне также понадобится общий макет writeToOut :-).
В своем приложении я использую реализацию InputStreamDataSource, которая принимает InputStream в качестве аргумента конструктора вместо File в 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");
}
}