4

FilterOutputStream.close()这是对 Java 8 中方法的更改,这给我们带来了一些问题。(见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由于deflaterStreamwraps boswhich wraps outStreamdeflaterStream.close()将调用bos.close()which 将调用outStream.close()which 关闭数据库的底层流。

try-with-resources 机制接下来会调用close(). bos由于bosextends 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

4 回答 4

2

这是FilterOutputStream. 这实现了 Closeable。此接口的合同规定:

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

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

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

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

你可以创建一个类,CloseOnceBufferedOutputStream扩展BufferedOutputStream. 它必须boolean记住它是否已经关闭,以便它可以满足合同。覆盖该close()方法以检查流是否已关闭。如果是这样,只是return

如果 Oracle 修复了底层错误,您的新类将毫无用处,但代码将继续工作。

于 2014-08-07T23:28:07.790 回答
1

只需不要在 and 中bos声明,这样它们就不会自动关闭。只有声明的 s 将被关闭。outStreamtryAutoClosable

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){}
    }
}
于 2014-08-07T06:54:21.957 回答
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 将被关闭,这将关闭其他人。

于 2014-08-07T06:55:29.723 回答
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 的未来更新中修复它。

于 2014-08-13T07:28:30.543 回答