[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儲存第一次由於關閉資源而產生的異常,其他的關閉資源產生的異常被隱藏,最後傳播此異常。