【问题标题】:Close resource quietly using try-with-resources使用 try-with-resources 悄悄关闭资源
【发布时间】:2011-10-16 21:51:03
【问题描述】:

是否可以忽略使用 try-with-resources 语句关闭资源时引发的异常?

例子:

class MyResource implements AutoCloseable{
  @Override
  public void close() throws Exception {
    throw new Exception("Could not close");
  }  
  public void read() throws Exception{      
  }
}

//this method prints an exception "Could not close"
//I want to ignore it
public static void test(){
  try(MyResource r = new MyResource()){
    r.read();
  } catch (Exception e) {
    System.out.println("Exception: " + e.getMessage());
  }
}

或者我应该继续以finally 关闭吗?

public static void test2(){
  MyResource r = null;
  try {
     r.read();
  }
  finally{
    if(r!=null){
      try {
        r.close();
      } catch (Exception ignore) {
      }
    }
  }
}

【问题讨论】:

    标签: java exception java-7 try-with-resources


    【解决方案1】:

    我实际上并不推荐这样做,但我能想到的唯一方法是检查异常的堆栈跟踪。它来自附近的 close 方法吗?

    基于https://stackoverflow.com/a/32753924/32453,任何捕获的异常都将是来自主块的“异常”、来自关闭调用的异常,或者来自带有“抑制”关闭调用的 try 块的异常。

    所以你只需要弄清楚它是否是关闭调用本身的异常,这显然是catch 的行:

    try (Resource myResource = new Resource()) {
    
    } catch (IOException mightBeFromClose) {
      int currentLine = new Throwable().getStackTrace()[0].getLineNumber();
      int lineOfCatch = currentLine - 1;
      String currentFilename = new Throwable().getStackTrace()[0].getFileName();
      boolean exceptionWasFromClose = Stream.of(mightBeFromClose.getStackTrace()).anyMatch(l -> l.getFileName().equals(currentFilename) && l.getLineNumber() == lineOfCatch);
      if (exceptionWasFromClose) {
        // ...
      }
    }
    

    还有一些需要考虑的事情:

    一般来说,不清楚您是否要处理来自 close 调用的 IOException 与来自 try 块内部的调用不同。如果关闭调用意味着它没有将所有数据刷新到文件中怎么办?您可能希望以同样的方式处理/对待它们。

    另一种选择:在块的末尾附近手动关闭资源(使用它自己的 try-catch)。通常允许双重关闭,因此您可以在那里捕获关闭异常。

    另一种可能性:改用普通的 try-catch-finally 模式,这里有一些方法可以让它稍微不那么难看:Java try-finally inside try-catch pattern 如果您没有多个资源,可能是一个选择。

    【讨论】:

      【解决方案2】:

      我在 coin-dev 邮件列表中找到了这个答案: http://mail.openjdk.java.net/pipermail/coin-dev/2009-April/001503.html

      5. close 方法的一些失败可以安全地忽略(例如, 关闭已打开以供读取的文件)。构造是否提供 这个?

      没有。尽管此功能看起来很有吸引力,但尚不清楚 增加复杂性是值得的。实际上,这些“无害 异常”很少发生,因此程序将不再健壮 如果这些异常被忽略。如果你觉得你必须忽略它们, 有一种解决方法,但它并不漂亮:

      static void copy(String src, String dest) throws IOException {
          boolean done = false;
          try (InputStream in = new FileInputStream(src)) {
              try(OutputStream out = new FileOutputStream(dest)) {
                  byte[] buf = new byte[8192];
                  int n;
                  while ((n = in.read(buf)) >= 0)
                      out.write(buf, 0, n);
              }
              done = true;
          } catch(IOException e) {
              if (!done)
                  throw e;
          }
      }
      

      【讨论】:

      • 不漂亮。现在还有其他想法吗?
      【解决方案3】:

      您可以在这里使用装饰器模式安静地关闭资源:

      public class QuietResource<T extends AutoCloseable> implements AutoCloseable{
          T resource;
          public QuietResource(T resource){
              this.resource = resource;
          }
          public T get(){
              return resource;
          }
          @Override
          public void close() {
              try {
                  resource.close();
              }catch(Exception e){
                  // suppress exception
              }
          }  
      }
      

      我个人不喜欢生成的语法,但也许这对你有用:

      public static void test(){
          try(QuietResource<MyResource> qr = new QuietResource<>(new MyResource())){
              MyResource r = qr.get();
              r.read();
          } catch (Exception e) {
              System.out.println("Exception: " + e.getMessage());
          }
      }
      

      如果您愿意将自己限制在处理接口并利用动态代理类,您可以做得更好:

      public class QuietResource<T> implements InvocationHandler {
      
          private T resource;
      
          @SuppressWarnings("unchecked")
          public static <V extends AutoCloseable> V asQuiet(V resource){
              return (V) Proxy.newProxyInstance(
                      resource.getClass().getClassLoader(),
                      resource.getClass().getInterfaces(),
                      new QuietResource<V>(resource));
          }
      
          public QuietResource(T resource){
              this.resource = resource;
          }
      
          @Override
          public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
              if(m.getName().equals("close")){
                  try {
                      return m.invoke(resource, args);
                  }catch(Exception e){
                      System.out.println("Suppressed exception with message: " + e.getCause().getMessage());
                      // suppress exception
                      return null;
                  }
              }
              return m.invoke(resource, args);
          }
      }
      

      那么假设你有:

      public interface MyReader extends AutoCloseable{
          int read();
      }
      

      使用实际的资源类:

      public class MyResource implements MyReader {
      
          public void close() throws Exception{
              throw new Exception("ha!");
          }
      
          public int read(){
              return 0;
          }
      }
      

      调用语法如下:

      public static void test(){
          try(MyReader r = QuietResource.asQuiet(new MyResource())){
              r.read();
          } catch (Exception e) {
              System.out.println("Exception: " + e.getMessage());
          }
      }
      

      如果您想开始包含库,例如 AOP 启用程序,您可以做得更好。然而,这些解决方案将与 JDK7 一起开箱即用,并且没有其他依赖项。

      【讨论】:

      • 您应该从close() 中删除throws Exception 以记录捕获的异常并简化使用。
      • @Mark Elliot 你可以实现用 throws 声明的接口方法,而类方法没有声明任何 throws。
      【解决方案4】:

      这是一种解决方案:

          boolean ok=false;
          try(MyResource r = new MyResource())
          {
              r.read();
              ok=true;
          }
          catch (Exception e)
          {
              if(ok)
                  ; // ignore
              else
                  // e.printStackTrace();
                  throw e;
          }
      

      如果ok==true 出现异常,则肯定来自close()

      如果ok==falsee 来自read() 或构造函数。 close() 仍然会被调用并且可能会抛出 e2,但无论如何 e2 都会被抑制。

      不经过这样的分析,代码是非常可读的。直观地说,如果ok==true,我们真正的工作已经完成,我们并不真正关心在资源之后会出现什么错误。

      【讨论】:

        猜你喜欢
        • 2020-01-01
        • 1970-01-01
        • 2015-08-13
        • 2016-09-29
        • 1970-01-01
        • 2013-09-12
        • 2017-12-04
        • 2012-01-23
        • 2015-02-14
        相关资源
        最近更新 更多