Java 8 FilterOutputStream异常

时间:2014-08-07 06:49:07

标签: java io try-with-resources

这是对Java 8中FilterOutputStream.close()方法的一个改变,它导致了一些问题。 (见http://hg.openjdk.java.net/jdk8/jdk8/jdk/rev/759aa847dcaf

在以前的Java版本中,以下代码无需抛出异常即可运行。但是,在Java 8下,当try-with-resources机制关闭流时,我们总是会遇到异常。

try( InputStream bis = new BufferedInputStream( inputStream );
     OutputStream outStream = payloadData.setBinaryStream( 0 );
     BufferedOutputStream bos = new BufferedOutputStream( outStream );
     DeflaterOutputStream deflaterStream = new DeflaterOutputStream(
         bos, new Deflater( 3 ) ) )
{
    fileSize = IOUtil.copy( bis, deflaterStream );
}

try-with-resources机制将首先在close()上调用deflaterStream。由于deflaterStream包裹bos包裹outStreamdeflaterStream.close()会调用bos.close(),它会调用outStream.close()来关闭数据库的基础流。< / p>

try-with-resources机制接下来会在close()上调用bos。由于bos扩展FilterOutputStreamflush()将首先在outStream上调用。但是,由于outStream已关闭,outStream.flush()会引发异常:java.sql.SQLException: Closed LOB

caused by: java.io.IOException: Closed LOB
     at oracle.jdbc.driver.OracleBlobOutputStream.ensureOpen(OracleBlobOutputStream.java:265)
     at oracle.jdbc.driver.OracleBlobOutputStream.flush(OracleBlobOutputStream.java:167)
     at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:141)
     at java.io.FilterOutputStream.close(FilterOutputStream.java:158)
     at com.blah.uploadFile(CustomerUploadFacade.java:162)
     ... 38 more
Caused by: java.sql.SQLException: Closed LOB
     at oracle.jdbc.driver.OracleBlobOutputStream.ensureOpen(OracleBlobOutputStream.java:257)
     ... 42 more

还有其他人遇到过此问题吗?如果是这样,你是如何解决它的?我们使用try-with-resources的方式有问题吗?

4 个答案:

答案 0 :(得分:2)

这是FilterOutputStream中的错误。这实现了Closable。此接口的合同声明:

  

关闭此流并释放与其关联的所有系统资源。如果流已经关闭,则调用此方法无效。

所以第二次调用.close()应该没有效果,但是在这种情况下调用flush()会抛出异常。

FilterOutputStream卡在继承层次结构中,因此在问题点应用修复程序并不容易。

未能将某些流声明为try块中的局部变量将导致FindBugs和Eclipse等工具将代码标记为资源使用情况。稍后查看此代码的勤奋开发人员可能会想到同样的事情。

您可以创建一个扩展CloseOnceBufferedOutputStream的类BufferedOutputStream。它将有一个boolean来记住它是否已经关闭以便它能够满足合同。重写close()方法以检查流是否已关闭。如果是这样,只需return

如果Oracle修复了底层错误,那么您的新类将无用,但代码将继续有效。

答案 1 :(得分:1)

不要在bos中声明outStreamtry,因此他们不会自动关闭。只有声明的AutoClosable才会被关闭。

JVM不会分析声明的AutoClosable s是否使用另一个构造,因此必须显式关闭它们中的每一个。如果他们的close()方法只能调用一次,则可能会导致您遇到的问题。

只声明deflaterStream这样:

try( InputStream bis = new BufferedInputStream( inputStream );
     DeflaterOutputStream deflaterStream = new DeflaterOutputStream(
        new BufferedOutputStream( payloadData.setBinaryStream( 0 ) ),
            new Deflater( 3 ) ) )
{
    fileSize = IOUtil.copy( bis, deflaterStream );
}

修改

点击此处的评论后,如果出现其他问题,您可以关闭payloadData.setBinaryStream( 0 )返回的信息流:

OutputStream outStream = null;
try( InputStream bis = new BufferedInputStream( inputStream );
     DeflaterOutputStream deflaterStream = new DeflaterOutputStream(
        new BufferedOutputStream( outStream = payloadData.setBinaryStream( 0 ) ),
            new Deflater( 3 ) ) )
{
    fileSize = IOUtil.copy( bis, deflaterStream );
}
catch (ExceptionsYouWantToCatch eywtc)
{
    if (outStream != null) {
        // Here you have the chance to close it
        try { outStream.close(); } catch(IOException ie){}
    }
}

答案 2 :(得分:0)

恕我直言,你不应该这样做但是......

您不需要声明您创建的所有输出流,而只需声明最外层的输出流。

try( InputStream bis = new BufferedInputStream( inputStream );
     DeflaterOutputStream deflaterStream = new DeflaterOutputStream(
         new BufferedOutputStream( payloadData.setBinaryStream( 0 ) ), new Deflater( 3 ) ) )
{
    fileSize = IOUtil.copy( bis, blobDeflaterStream );
}

这样deflatorStream将被关闭,这将关闭其他人。

答案 3 :(得分:0)

我们的最终解决方案是自行修复FilterOutputStream:

public void close() throws IOException {
    if (!closed) {
        closed = true;
        try (OutputStream ostream = out) {
            flush();
        }
    }
}

private boolean closed = false;

此类包含在一个小的JAR文件中,并添加到引导类路径中:-Xbootclasspath/p:%APP_HOME%\lib\jdkFix.jar

希望Oracle能够承认这个问题是一个错误,并在将来的Java 8更新中修复它。