包含多个可关闭资源的类的正确习惯用法



对于用户代码,有几个选项可以正确关闭多个资源:

1. 试用资源

try (
A a = new A();
B b = new B();
C c = new C()
) {
// ...
}

除了漂亮和简短之外,它也是正确的。

  • 它将正确关闭abc需要关闭的任何。
  • 此外,如果从正文中抛出异常,它还将"抑制"关闭期间发生的异常(这是对try/finally的改进,可以在此处阅读 https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html)

阿拉伯数字。番石榴Closer

对于JDK7之前的版本,有Guava的Closer,其用法如下:

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

虽然稍长,但它也很好用(查看此处 https://github.com/google/guava/wiki/ClosingResourcesExplained#closer 了解更多信息)


持有多个资源的对象呢?

假设我有:

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

此代码存在多个问题:

  • 如果从构造函数引发异常,则不会关闭任何内容(调用方没有要调用close的实例)
  • 如果从close抛出异常,某些资源将不会关闭

12都没有这些问题。然而:

  • 尝试资源显然不能使用,因为 P 的生存期由调用方控制
  • 番石榴Closer似乎也不能使用。虽然它更灵活,但它不支持关闭和重新抛出,这是构造函数所必需的

对于没有太多样板的 N 资源,这里的正确模式是什么?溶液还应具有12的抑制特性

如果从构造函数中抛出异常,则不会关闭任何内容(调用方没有要调用close的实例)

您可以捕获在单个资源初始化期间引发的任何异常,并关闭到目前为止初始化的所有资源,并抛出一个表示初始化失败的异常。

如果从关闭引发异常,则不会关闭某些资源

与上面相同,但这次表示关闭某些资源失败。

此解决方案做出以下假设:

如果您尝试使用具有三个资源的资源A, B and C原始代码片段,

  1. 如果其中任何一个初始化失败或 try 块抛出异常,并且
  2. 其中一个或多个的 close 方法抛出异常,

则仅抛出从1抛出的异常,抑制来自2的异常,可以通过调用可抛出的getSuppressed来获取。

但是,当您使用包装类抽象单个资源时,我认为我们不必具有相同的行为,即close方法失败(异常)添加到抑制的异常中。换句话说,所有这些资源都必须由包装器抽象,并且不得引发特定于一个资源的任何异常。


整个初始化代码包装在单个try..catch块中。如果任何资源初始化失败,它将关闭所有打开的资源并抛出一个 Exception,以表示包装器资源的初始化失败。如果任何close在此处失败,则会将其静音(并且调用方无法通过getSuppressed获取)。

关闭包装器资源时,每个单独的资源都将关闭,如果其中任何一个失败,则会再次抛出一个表示关闭包装器资源失败的异常

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

用法:

try (Resources resources = new Resources()) {
....
} catch (Exception e) {
....
}

我建议这样做:

public close() throws ... {
try (A aa = a;
B bb = b;
C cc = c) {
// empty
}
}

我们只是使用标准的"试用资源"机制来关闭以前打开的资源。 这将处理abcnull的情况,以及close()调用抛出异常的情况。

对于构造函数:

public P() throws ... {
try {
a = new A();
b = new B();
c = new C();
} finally {
if (!(a != null && b != null && c != null)) {
close();
}
}

如果要抑制构造函数中close()引发的异常,则情况会更复杂。

您可以使用 execute around 方法模式对资源包装类的用户隐藏资源的打开和关闭。这样,您将确保资源始终处于关闭状态。您应该为不同的用例添加单独的操作方法。仅当这是公共资源并由应用程序的许多部分使用时,这才有用。

这是一个示例

public class ResourceWrapper {
private A a;
private B b;
private C c;
private ResourceWrapper() {
// add try catch if you have to, after cleanup then throw exception if ithappens
a = new A();
b = new B();
c = new C();
}
/**
* add required operation methods
*/
public ResourceWrapper op1() {
// do some operations
return this;
}
public ResourceWrapper op2() {
// if additional add or different
return this;
}
// close everything here
private void close() {
// check null if you have to
// add try catch if you have to
c.close();
b.close();
a.close();
}
public static void use(Consumer<ResourceWrapper> consumer) {
ResourceWrapper resource = null;
try {
resource = new ResourceWrapper();
consumer.accept(resource);
}
finally {
if(resource!=null) {
resource.close();
}
}
}
}
public class SampleResourceUser {
/*
* This represents the user of the Resource,
* User only cares about which operations that needs to be done on the resource.
* Opening and closing the resource wrapped around the operation methods by the owner of the Resource.
*
*/
public static void main(String[] args) {
ResourceWrapper.use(resource->resource.op1().op2());
}
}

最新更新