1. 程式人生 > >[Guava原始碼日報](9)Closeables

[Guava原始碼日報](9)Closeables

它的作用是收集可以關閉的資源並在合適的時候關閉它們。

如下使用:

Closer closer = Closer.create();
try {
    InputStream in = closer.register(openInputStream());
    OutputStream out = closer.register(openOutputStream());
    // do stuff
} catch (Throwable e) {
    // ensure that any checked exception types other than IOException that could be thrown are
// provided here, e.g. throw closer.rethrow(e, CheckedException.class); throw closer.rethrow(e); } finally { closer.close(); }

使用closer會保證:

每個成功註冊的可以關閉的資源會在合適的時候關閉

如果在try塊中丟擲了Throwable異常,finally塊中不會丟擲任何由於關閉資源而產生的異常(被隱藏)。

如過try塊中沒有exceptions或errors丟擲,第一個試圖去關閉資源而產生的異常將會被丟擲。

隱藏的異常不會被丟擲,使用隱藏的方法依賴於當前Java的版本。

Java 7+: 使用Throwable.addSuppressed(Throwable)隱藏異常。

Java 6: 通過記錄日誌的方法來隱藏異常。

1. Create()

通過呼叫靜態方法create()來建立一個Closer物件。

public static Closer create() {
    return new Closer(SUPPRESSOR);
  }

2. <C extends Closeable> C register(C closeable)

建立好Closer物件後就可以註冊需要關閉的資源了,如InputStream,OutputStream等。

public <C extends
Closeable> C register(@Nullable C closeable) { if (closeable != null) { stack.addFirst(closeable); } return closeable; }

通過一個棧來儲存我們註冊的需要關閉的資源。

private final Deque<Closeable> stack = new ArrayDeque<Closeable>(4);

在大多數情況下只需要2個元素的空間即可,一個用於讀資源,一個用於寫資源,所以使用最小的arrayDeque就可以。

3. rethrow()過載函式

3.1 RuntimeException rethrow(Throwable e)

儲存給定的異常Throwable,並重新丟擲。

每當呼叫這個方法時,都會把這個異常儲存到欄位thrown中,並重新丟擲。如果給定的異常是IOException,RuntimeException或者Error就會重新丟擲,否則把這個異常包裝成RuntimeException異常丟擲。這個方法沒有返回值,當給定的異常IOException時,則會丟擲IOException。

public RuntimeException rethrow(Throwable e) throws IOException {
    checkNotNull(e);
    thrown = e;
    Throwables.propagateIfPossible(e, IOException.class);
    throw new RuntimeException(e);
  }

如下呼叫:

throw closer.rethrow(e);
3.2 RuntimeException rethrow(Throwable e,Class<X> declaredType)

如果給定的異常是IOException,RuntimeException,Error或者給定的異常型別X就會重新丟擲,否則把這個異常包裝成RuntimeException異常丟擲。

public <X extends Exception> RuntimeException rethrow(Throwable e,
      Class<X> declaredType) throws IOException, X {
    checkNotNull(e);
    thrown = e;
    Throwables.propagateIfPossible(e, IOException.class);
    Throwables.propagateIfPossible(e, declaredType);
    throw new RuntimeException(e);
  }

如下呼叫:

throw closer.rethrow(e, ...)
3.3 RuntimeException rethrow(Throwable e, Class<X1> declaredType1, Class<X2> declaredType2)

如果給定的異常是IOException,RuntimeException,Error或者給定異常型別X1,X2就會重新丟擲,否則把這個異常包裝成RuntimeException異常丟擲。

public <X1 extends Exception, X2 extends Exception> RuntimeException rethrow(
      Throwable e, Class<X1> declaredType1, Class<X2> declaredType2) throws IOException, X1, X2 {
    checkNotNull(e);
    thrown = e;
    Throwables.propagateIfPossible(e, IOException.class);
    Throwables.propagateIfPossible(e, declaredType1, declaredType2);
    throw new RuntimeException(e);
  }

如下呼叫:

throw closer.rethrow(e, ...)

4. Suppressor

Suppressor是一個介面,介面中只有一個方法suppress。

/**
   * Suppression strategy interface.
   */
  @VisibleForTesting interface Suppressor {
    /**
     * Suppresses the given exception ({@code suppressed}) which was thrown when attempting to close
     * the given closeable. {@code thrown} is the exception that is actually being thrown from the
     * method. Implementations of this method should not throw under any circumstances.
     */
    void suppress(Closeable closeable, Throwable thrown, Throwable suppressed);
  }

Suppressor有兩種不同的實現,一種是Java 6中通過記錄日誌來隱藏異常,另一種是Java 7中通過使用addSuppressed()方法而隱藏異常。

4.1 通過日誌記錄隱藏異常
@VisibleForTesting static final class LoggingSuppressor implements Suppressor {
    static final LoggingSuppressor INSTANCE = new LoggingSuppressor();
    @Override
    public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) {
      // log to the same place as Closeables
      Closeables.logger.log(Level.WARNING,
          "Suppressing exception thrown when closing " + closeable, suppressed);
    }
  }
4.2 通過addSuppressed方法隱藏異常
@VisibleForTesting static final class SuppressingSuppressor implements Suppressor {
    static final SuppressingSuppressor INSTANCE = new SuppressingSuppressor();
    static boolean isAvailable() {
      return addSuppressed != null;
    }
    static final Method addSuppressed = getAddSuppressed();
    private static Method getAddSuppressed() {
      try {
        return Throwable.class.getMethod("addSuppressed", Throwable.class);
      } catch (Throwable e) {
        return null;
      }
    }
    @Override
    public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) {
      // ensure no exceptions from addSuppressed
      if (thrown == suppressed) {
        return;
      }
      try {
        addSuppressed.invoke(thrown, suppressed);
      } catch (Throwable e) {
        // if, somehow, IllegalAccessException or another exception is thrown, fall back to logging
        LoggingSuppressor.INSTANCE.suppress(closeable, thrown, suppressed);
      }
    }
  }

通過反射機制判斷Throwable是否有addsuppressed()方法,通過呼叫isAvailable()方法判斷。

Throwable.class.getMethod("addSuppressed", Throwable.class);

在suppress()方法中執行addSuppressed.invoke(thrown, suppressed)出現異常時會使用記錄日誌的方法隱藏異常。

5. void close()

public void close() throws IOException {
        Throwable throwable = thrown; // 方法變數throwable儲存最近一次呼叫rethrow()丟擲的異常
        // 關閉棧中的closeable(先進後出)
        while (!stack.isEmpty()) {
            Closeable closeable = stack.removeFirst();
            try {
                closeable.close(); // 試圖關閉資源
            } catch (Throwable e) { // 關閉資源出現異常,隱藏此異常
                if (throwable == null) { // 未呼叫過rethrow()
                    throwable = e; // 如果未呼叫rethrow(), throwable是第一次由於關閉資源而產生的異常
                } else { // 否則關閉資源而產生的異常被隱藏
                    suppressor.suppress(closeable, throwable, e); // 隱藏異常 兩種實現 視Java版本而定 
                }
            }
        }
        // 如果未呼叫過rethrow,且在關閉資源過程中出現異常,則傳播此異常
        if (thrown == null && throwable != null) {
            Throwables.propagateIfPossible(throwable, IOException.class);
            throw new AssertionError(throwable); // not possible
        }
    }

方法變數throwable儲存最近一次呼叫rethrow()丟擲的異常,只有在rethrow方法中才會為throw變數賦值。

如果呼叫過rethrow()方法(thrown != null),則所有在關閉資源過程中出現的異常都被隱藏。如果未呼叫過rethrow()方法(thrown == null), 則throwable儲存第一次由於關閉資源而產生的異常,其他的關閉資源產生的異常被隱藏,最後傳播此異常。