For user code, there are a couple of options for correctly closing multiple resources:
try (
A a = new A();
B b = new B();
C c = new C()
) {
// ...
}
Apart from being nice and short, it is also correct.
a
, b
and c
needs closing.try/finally
as can be read here https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html)Closer
For pre-JDK7 there is Guava's Closer
which is used like:
Closer closer = Closer.create();
try {
A a = closer.register(new A());
B b = closer.register(new B());
C c = closer.register(new C());
// ...
} catch (Throwable e) { // must catch Throwable
throw closer.rethrow(e);
} finally {
closer.close();
}
While slightly longer, it also works pretty good (check here https://github.com/google/guava/wiki/ClosingResourcesExplained#closer for more info)
Say I have:
public class P implements AutoCloseable {
private A a;
private B b;
private C c;
public P() {
a = new A();
b = new B();
c = new C();
}
public close() {
c.close();
b.close();
a.close();
}
}
There are multiple problems with this code:
close
)close
, some resources will not be closedNeither 1 nor 2 suffered from these issues. However:
Closer
seems cannot be used either. While it is more flexible, it does not support close-and-rethrow, which is necessary from the constructorWhat is the correct pattern here for N resources without too much boilerplate? The solution should also have the suppression property of 1 and 2
The try -with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try -with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.
If an exception is thrown from within a Java try-with-resources block, any resource opened inside the parentheses of the try block will still get closed automatically. The throwing of the exception will force the execution to leave the try block, and this will force the automatic closing of the resource.
A try-with-resources statement can have catch and finally blocks just like an ordinary try statement. In a try-with-resources statement, any catch or finally block is run after the resources declared have been closed.
In the try-with-resources method, there is no use of the finally block. The file resource is opened in try block inside small brackets. Only the objects of those classes can be opened within the block which implements the AutoCloseable interface, and those objects should also be local.
If exception is thrown from the constructor, nothing will be closed (the caller does not have the instance on which to call close)
You can catch any Exception that is thrown during the initialization of the individual resources and close all the resources initialized so far and throw back one Exception denoting initialization failed.
If exception is thrown from close, some resources will not be closed
Same as above, but this time it denoting closing of some resources failed.
This solution makes the below assumption:
If you take your original code snippet having a try with resources with three resources A, B and C
,
then only the Exception thrown from 1 is thrown back and the exception(s) from 2 is suppressed and can be obtained by calling the Throwable's getSuppressed
.
However, when you are abstracting the individual resources with a wrapper class, I don't believe we must have the same behaviour i.e, adding close
method failures (exceptions) to suppressed exceptions. In other words, all those resources must be abstracted by the wrapper and must not throw any exception specific to one resource.
The entire initialization code is wrapped in a single try..catch
block. If any of the resource initialization fails, it closes all the opened resources and throws back one Exception to denote that the initialization of the wrapper resource failed. If any of the close
fails here, it is silenced (and cannot be obtained via getSuppressed
by the caller).
When closing the wrapper resource, each of the individual resources are closed and if any of them fails, again one Exception denoting the closing of the wrapper resource failed is thrown back.
Let Resources
be the class that holds multiple closeable resources.
public class Resources implements AutoCloseable {
private MyCloseable1 myCloseable1;
private MyCloseable2 myCloseable2;
public Resources() {
try {
myCloseable1 = new MyCloseable1();
myCloseable2 = new MyCloseable2();
} catch (Exception e) {
close(false, myCloseable1, myCloseable2);
throw new RuntimeException("Initialization failed");
}
}
@Override
public void close() throws Exception {
close(true, myCloseable1, myCloseable2);
}
private void close(boolean throwExceptionIfFailed, AutoCloseable... autoCloseables) {
boolean closeFailed = false;
for (AutoCloseable autoCloseable : autoCloseables) {
try {
if (autoCloseable != null) {
autoCloseable.close();
}
} catch (Exception e) {
//Add logs here.
closeFailed = true;
}
}
/*
Using Java 8 streams and reduce.
closeFailed = Arrays.stream(autoCloseables)
.filter(Objects::nonNull)
.reduce(false, (isFailed, autoCloseable) -> {
try {
autoCloseable.close();
} catch (Exception e) {
return true;
}
return isFailed;
}, (isFailed1, isFailed2) -> isFailed1 || isFailed2);
*/
if (closeFailed && throwExceptionIfFailed) {
throw new RuntimeException("Closing of Resources failed");
}
}
}
Usage:
try (Resources resources = new Resources()) {
....
} catch (Exception e) {
....
}
I would suggest doing this:
public close() throws ... {
try (A aa = a;
B bb = b;
C cc = c) {
// empty
}
}
We are simply using the standard try-with-resource mechanism to close the resources that were opened previously. This will deal with the cases where a
, b
or c
are null
, and where the close()
calls throw an exception.
For the constructor:
public P() throws ... {
try {
a = new A();
b = new B();
c = new C();
} finally {
if (!(a != null && b != null && c != null)) {
close();
}
}
It is more complicated if you want to suppress exceptions thrown by close()
in the constructor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With