如何将InputStream转换为DataHandler?

31

我正在开发一个Java Web应用程序,其中文件将存储在数据库中。最初,我们通过简单地在结果集上调用getBytes来检索已在数据库中的文件:

byte[] bytes = resultSet.getBytes(1);
...

这个字节数组然后使用明显的构造器转换为DataHandler

dataHandler = new DataHandler(bytes, "application/octet-stream");

这个方案在我们开始尝试存储和检索更大的文件时表现得很好,但是将整个文件内容转储到字节数组中,然后构建一个DataHandler显然需要太多内存。

我立即想到的是使用 getBinaryStream 从数据库中获取数据流,然后以一种节省内存的方式将该InputStream转换为DataHandler。不幸的是,似乎没有直接将InputStream转换为DataHandler的方法。另一个我一直在尝试的想法是从InputStream读取数据块,并将它们写入DataHandlerOutputStream。但是...我找不到创建返回非空OutputStream的“空”DataHandler的方法当我调用getOutputStream时....

有人做过这个吗?非常感谢任何帮助或指引。

8个回答

24

Kathy Van Stone的答案的实现:

首先,创建一个辅助类,从InputStream创建一个DataSource:

public class InputStreamDataSource implements DataSource {
    private InputStream inputStream;

    public InputStreamDataSource(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return inputStream;
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Not implemented");
    }

    @Override
    public String getContentType() {
        return "*/*";
    }

    @Override
    public String getName() {
        return "InputStreamDataSource";
    }
}

然后您可以从InputStream创建一个DataHandler:

DataHandler dataHandler = new DataHandler(new InputStreamDataSource(inputStream))

导入:

import javax.activation.DataSource;
import java.io.OutputStream;
import java.io.InputStream;

每次调用 getInputStream 应该返回一个新的 InputStream - husayt
请问您能解释一下这个原因吗? - Gordak
1
由于重复使用InputStream可能会出现“流关闭”的IOException。 - jackmis
1
通用的InputStream只能被读取一次,没有理由将其包装成新的流。 - bugs_
1
可以使用 org.apache.cxf.attachment.AttachmentDataSource 替换这个答案中的类(ct = ContentType)。 - Albert Hendriks

19

我也遇到了这个问题。如果你的源数据是一个byte[]Axis已经有一个包装InputStream并创建DataHandler对象的类。以下是代码:

// This constructor takes byte[] as input
ByteArrayDataSource rawData = new ByteArrayDataSource(resultSet.getBytes(1));
DataHandler data = new DataHandler(rawData);
yourObject.setData(data);

相关导入

import javax.activation.DataHandler;
import org.apache.axiom.attachments.ByteArrayDataSource;

7
因为它将所有数据加载到内存中,所以在处理大量数据时会出现问题。 - Jordan Silva
1
还有其他的DataSource接口实现方式:我使用了import javax.mail.util.ByteArrayDataSource;。 - Croo

18

我的建议是编写一个自定义类来实现DataSource接口,这个类将包装您的InputStream。然后创建一个DataHandler对象并将创建好的DataSource传递给它。


啊,这是个好主意。我有机会的时候会试一下的。 - pcorey
我也曾这样想过。但是要注意,那么就必须在 ResultSet 打开的时候,在“循环内部”使用 DataHandler(消耗其输入)。例如,你可能无法将 DataHandler 对象传递到上层。 - leonbloy
@leonbloy 所述目标是在不从结果集中复制数据的情况下处理数据。这意味着无论如何,结果集必须始终保持打开状态。 - Kathy Van Stone

4

我知道这很老了...那个 bug 是真的吗? - Cris
3
API说过这件事。然而,它说要么返回一个新的流,要么抛出一个异常。从技术上讲,这意味着第一次返回一个流,然后才会抛出异常。我假设大多数框架只会获取一次流。 - Steve11235
链接(实际上)已经失效:它重定向到一个通用页面,“Java Bug Database” - Peter Mortensen
好的,OP已经离开了:“上次出现是在11年前”。 - Peter Mortensen

2

bugs_'s code 对我来说不起作用。我使用 DataSource 来创建邮件的 附件(从具有 inputStreamname 的对象中),但附件的内容丢失了。

看起来 Stefan 是正确的,每次都必须返回一个新的 inputStream。至少在我的特定情况下是这样。以下实现解决了这个问题:

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");
    }
}

0

这里有一个针对Spring Boot org.springframework.core.io.Resource对象的解答,我认为很多人都是通过这种方式来到这里的。请注意,由于我将PNG文件插入HTML格式的电子邮件中,因此您可能需要修改下面代码中的内容类型。

注意:正如其他人所提到的那样,仅仅附加InputStream是不够的,因为它会被多次使用。只需映射到Resource.getInputStream()即可解决问题。

public class SpringResourceDataSource implements DataSource {
    private Resource resource;

    public SpringResourceDataSource(Resource resource) {
        this.resource = resource;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return resource.getInputStream();
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Not implemented");
    }

    @Override
    public String getContentType() {
        return "image/png";
    }

    @Override
    public String getName() {
        return "SpringResourceDataSource";
    }
}

这个类的使用方法如下:

PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource logoImage = pathMatchingResourcePatternResolver.getResource("/static/images/logo.png");
MimeBodyPart logoBodyPart = new MimeBodyPart();
DataSource logoFileDataSource = new SpringResourceDataSource(logoImage);

logoBodyPart.setDataHandler(new DataHandler(logoFileDataSource));

0
我曾遇到这种情况:在使用记录处理程序和 MTOM 功能时,从 {{code:DataSource}} 请求了两次 {{code:InputStream}}。
通过 这个代理流解决方案 ,我的实现运行良好。
import org.apache.commons.io.input.CloseShieldInputStream;
import javax.activation.DataHandler;
import javax.activation.DataSource;
...

private static class InputStreamDataSource implements DataSource {
    private InputStream inputStream;

    @Override
    public InputStream getInputStream() throws IOException {
        return new CloseShieldInputStream(inputStream);
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Not implemented");
    }

    @Override
    public String getContentType() {
        return "application/octet-stream";
    }

    @Override
    public String getName() {
        return "";
    }
}

-1
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

 DataSource ds = new ByteArrayDataSource(convertHtmlToPdf("<span>html here</span>"), "application/pdf");

 DataHandler dataHandler = new DataHandler(ds);

public static byte[] convertHtmlToPdf(String htmlString) throws IOException, DocumentException {
    Document document = new Document();

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    PdfWriter writer = PdfWriter.getInstance(document, out);
    document.open();

    InputStream in = IOUtils.toInputStream(htmlString);
    XMLWorkerHelper.getInstance().parseXHtml(writer, document, in);
    document.close();

    return out.toByteArray();
}

可能出现错误:meta标签必须关闭。<meta></meta>

这个"answer"的意义是什么? - Peter Mortensen
你能提供一些上下文吗? - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接