【问题标题】:Closing resources created in failed constructor关闭在失败的构造函数中创建的资源
【发布时间】:2020-12-09 15:16:57
【问题描述】:

假设我有一个类C,其中包含需要作为成员变量关闭的资源。

public class C {
    private ClosableResource1 closableResource1;
    private ClosableResource2 closableResource2;
    .....

    public C(){
        closableResource1 = new ClosableResource1(); 
        closableResource2 = new ClosableResource2(); 
        .....
        // some logic that can fail
    }
    
    close(){
        closableResource1.close()
        closableResource2.close()
        .....
    }
}

如果构造函数成功,我可以确定close() 最终会被某个实体管理器调用,并且所有资源都将被释放。 但是如何确保在构造函数失败时关闭资源?失败可能是因为我在构造函数中有额外的逻辑可以抛出异常,或者我得到了一些超出我控制的RuntimeException

我想到的一些事情:

  • try-catch 块包装构造函数主体。然后,假设我有很多可关闭的成员,我必须在 catch 块中有一个大的 if 语句,检查哪些资源已经在初始化并且只关闭它们。
  • ClosableResources 创建卸载到某个init() 函数。然后,每次尝试使用该对象时,我都必须确保 init() 成功。

有什么优雅的解决方案吗?或者这是否比那个更具体?

【问题讨论】:

  • 有理由不使用try-with-resources吗?当在您的构造函数中使用时,这可能是一个可行的选择。可能更好的是定义 init() 函数并在对象构造之后显式调用它们,因为这样您就不会将资源管理绑定到对象创建。
  • 我认为这样做根本不是一个好主意。传递您想要处理的资源,不要将资源管理与其使用混为一谈。
  • @daniu 当资源是特定于实现并且仅与将使用它们的类相关时传递资源是否仍然有意义?假设我有一些 FileReader 类,它在后台管理一个 Mongo 数据库,所以每个 FileReader 实例自然应该可以访问一些 Mongo 客户端。为什么FileReader 的用户甚至应该知道 Mongo,更不用说将客户端传递给它了?
  • @Starless 您应该始终避免在构造函数中创建对象。在您实例化 C 的地方,您可以尝试首先实例化您的可关闭资源,然后传递给 C - 如果存在,则无需担心 C 的状态发生错误。
  • @Starless 您可以创建一个中间类来为您创建实例而无需打扰客户端,例如一个知道 MongoDB 并具有方法 createFilereader 的“FileReaderService”,它将返回一个创建的阅读器使用适当的 Mongo 实例。然后客户端只需要知道服务就可以创建对象。

标签: oop


【解决方案1】:

您可以执行以下操作:

public class C {
    private List<AutoCloseable> closableResources = new ArrayList();
    private ClosableResource1 closableResource1;
    private ClosableResource2 closableResource2;
    .....

    public C() {
        closableResource1 = new ClosableResource1();
        closableResources.add(closableResource1)
        closableResource2 = new ClosableResource2();
        closableResources.add(closableResource2);
        .....
        try {
            // some logic that can fail
        } catch(Exception e) {
            close();
        }
    }
    
    close(){
        for (AutoCloseable closableResource : closableResources) {
            if (closableResource != null) {
                closableResource.close();
            }
        }
    }
}

【讨论】:

    【解决方案2】:

    用 try-catch 包围你的代码并在 catch 中关闭你的所有资源是这里正确的解决方案。另请阅读有关方法 finalize() 的信息(这是一个tutorial)。一般来说,我会推荐一种清理所有资源的方法(就像您建议的方法 close(),我会通过 cleanup() 调用它)并在您的 catch 部分和您的 finalize() 方法中调用该方法

    【讨论】:

      【解决方案3】:

      我问并回答了一个非常相似的问题here。构造函数成功或完全失败(即不保留任何资源)非常重要。为了实现这一点,我将在每个资源创建语句之后添加一个 try-catch 块。 catch 块关闭资源并重新抛出异常,因此它不会丢失:

        public C() {
          closableResource1 = new ClosableResource1();
          closableResource2 = new ClosableResource2();
          try {
            // .....
            // some logic that can fail and throw MyCheckedException or some RuntimeException
          } catch (RuntimeException | MyCheckedException e) {
            try {closableResource1.close();} catch (Exception ignore) {}
            try {closableResource1.close();} catch (Exception ignore) {}
            throw e;
          }
        }
      

      如果创建资源可能失败,您需要嵌套的 try-catch 块,如 here 所示。

      【讨论】:

      • 如果c1.close() 本身引发异常,则此操作失败。
      • 没错,在这种情况下,您可以捕捉并忽略它。我改变了答案。
      【解决方案4】:

      这是一个疯狂的想法:创建一个名为 DefusableCloser 的类(您可以“化解”它,就像使爆炸装置变得安全一样):

      class DefusableCloser implements AutoCloseable {
        boolean active = true;
        final AutoCloseable closeable;
      
        DefusableCloser(AutoCloseable closeable) {
          this.closeable = closeable;
        }
      
        @Override public void close() throws Exception {
          if (active) closeable.close();
        }
      }
      

      现在您可以在 try-with-resources 块中使用它:

      c1 = new CloseableResource();
      try (DefusableCloseable d1 = new DefusableCloseable(c1)) {
        c2 = new CloseableResource();
        try (DefusableCloseable d2 = new DefusableCloseable(c2)) {
          // Do the other stuff which might fail...
      
          // Finally, deactivate the closeables.
          d1.active = d2.active = false;
        }
      }
      

      如果执行未到达d1.active = d2.active = false;,则两个可关闭对象(或一个,如果异常是在创建第二个资源时)将被关闭。如果执行确实到达该行,它们将不会被关闭,您可以使用它们。

      这样做的好处是可以正确处理异常。

      请注意,顺序很重要:不要试图先创建两个 CloseableResource,然后再创建两个 DefusableCloseable:这样做不会处理创建第二个 CloseableResource 的异常。并且不要将 CloseableResources 的创建放入 TWR,因为这会保证它们的关闭。


      要关闭类中的资源 close() 方法,您还可以使用 try-with-resources 来确保关闭两个资源:

      try (c1; c2) {}
      

      您实际上不必在 TWR 语法中声明新变量:您只需有效地说“之后关闭此现有变量的资源”,如下所示。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-04-15
        • 2013-08-26
        • 2011-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多