[Guava源码日报](9)Closeables

简介: 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunnyYoona/article/details/71080196 它的作用是收集可以关闭的资源并在合适的时候关闭它们。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunnyYoona/article/details/71080196

它的作用是收集可以关闭的资源并在合适的时候关闭它们。

如下使用:

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保存第一次由于关闭资源而产生的异常,其他的关闭资源产生的异常被隐藏,最后传播此异常。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
GitHub阅读量最高的文章竟是图解Java,不愧是Alibaba内部资料
这篇《Java基础知识总结》是 JavaGuide 上阅读量最高的一篇文章,由于我对其进行了重构完善并且修复了很多小问题,所以,在头条同步一下!
|
8月前
|
设计模式 NoSQL Java
GitHub上标星65k+超火的《Java大厂面试》,分享整理的PDF版本
不论是校招还是社招都避免不了各种面试。笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个有章可循‘说的意思只是说应对技术面试是可以提前准备。
104 0
|
Web App开发 存储 JavaScript
💖每天拿出20分钟,带你入门涨薪3k的ElasticSearch全文搜索引擎4️⃣
Spring Data 是一个用于简化数据库、非关系型数据库、索引库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持 map-reduce 框架和云计算数据服务。 Spring Data 可以极大的简化 JPA(Elasticsearch„)的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了 CRUD 外,还包括如分页、排序等一些常用的功能。Spring Data 的官网
151 0
💖每天拿出20分钟,带你入门涨薪3k的ElasticSearch全文搜索引擎4️⃣
|
JSON 自然语言处理 Java
Java实现英汉词典API初版发布在Maven
基于ECDICT词典数据, 开发一个Java库实现词典接口, 发布到Maven主库. Publish Java library to provide English-to-Chinese dictionary API on Maven Central.
1374 0
guava_学习_00_资源帖
一、精选 1、Google Guava 官方教程       二、参考资源 1、Google Guava官方教程(中文版) 2、使用Guava编写优雅代码 3、Google guava工具类的介绍和使用
1055 0
|
IDE Java 开发工具
Github热度周排行第一,阿里Java规约插件开发之路曝光
10月14号,云栖大会研发效能专场,《阿里巴巴Java开发手册》IDE插件(包括PMD规则实现库)正式对外开源,并且迅速占领Github开源热度周排行榜第一的位置,作为项目组的一员,看到业界对该项目的关注及认可,发自内心的高兴。高兴之余,分享一下在整个过程中的一些心得体会。
4861 0
|
安全 容器 Java
[Guava源码日报](8)ImmutableCollection
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunnyYoona/article/details/71080164 不可变集合,顾名思义就是说集合是不可被修改的。
959 0