正确的类,其中包含多个可关闭的资源

时间:2018-03-11 06:13:55

标签: java

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

1。尝试与 - 资源

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

除了好又短,它也是正确的。

2。番石榴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引发异常,则某些资源将无法关闭

1 2 都没有遇到这些问题。但是:

  • 尝试使用资源显然无法使用,因为P的生命周期由调用者控制
  • 似乎也不能使用番石榴Closer。虽然它更灵活,但它不支持close-and-rethrow,这是构造函数
  • 所必需的

没有太多样板的N资源的正确模式是什么?该解决方案还应具有 1 2 的抑制属性

3 个答案:

答案 0 :(得分:1)

您可以隐藏资源包装器类中的资源的打开和关闭。用户执行方法模式。这样您就可以确保始终关闭资源。您应该为不同的用例添加单独的操作方法。只有当这是一个公共资源并且被应用程序的许多部分使用时,这才有用。

这是一个示例

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

答案 1 :(得分:1)

  

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

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

  

如果从关闭中抛出异常,则不会关闭某些资源

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

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

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

  1. 如果其中任何一个的初始化失败或者try块抛出异常并且
  2. 其中一个或多个的close方法抛出异常,
  3. 然后只返回从 1 抛出的异常,并且 2 中的异常被抑制,并且可以通过调用Throwable&#39;来获得{1}}。

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

    整个初始化代码包含在一个close块中。如果任何资源初始化失败,它将关闭所有打开的资源并返回一个Exception ,以表示包装器资源的初始化失败。如果try..catch中的任何一个在此处失败,则会被静音(并且无法通过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");
            }
        }
    }
    

答案 2 :(得分:1)

我建议这样做:

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

我们只是使用标准的try-with-resource机制来关闭之前打开的资源。这将处理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()抛出的异常,则会更复杂。