这是对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
包裹outStream
,deflaterStream.close()
会调用bos.close()
,它会调用outStream.close()
来关闭数据库的基础流。< / p>
try-with-resources机制接下来会在close()
上调用bos
。由于bos
扩展FilterOutputStream
,flush()
将首先在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的方式有问题吗?
答案 0 :(得分:2)
这是FilterOutputStream
中的错误。这实现了Closable。此接口的合同声明:
关闭此流并释放与其关联的所有系统资源。如果流已经关闭,则调用此方法无效。
所以第二次调用.close()
应该没有效果,但是在这种情况下调用flush()
会抛出异常。
FilterOutputStream卡在继承层次结构中,因此在问题点应用修复程序并不容易。
未能将某些流声明为try
块中的局部变量将导致FindBugs和Eclipse等工具将代码标记为资源使用情况。稍后查看此代码的勤奋开发人员可能会想到同样的事情。
您可以创建一个扩展CloseOnceBufferedOutputStream
的类BufferedOutputStream
。它将有一个boolean
来记住它是否已经关闭以便它能够满足合同。重写close()
方法以检查流是否已关闭。如果是这样,只需return
。
如果Oracle修复了底层错误,那么您的新类将无用,但代码将继续有效。
答案 1 :(得分:1)
不要在bos
中声明outStream
和try
,因此他们不会自动关闭。只有声明的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更新中修复它。