关于Java中的资源关闭,是一个常见的问题,也是最容易被初级程序员忽略的一个问题,这个问题的严重性,吃过亏的人都知道,不需多说。之所以会出现这个问题,主要还是在Java 7之前,语言没有很好地提供资源管理的语法。我们先看下面的代码:
Java 代码01 | InputStream inputStream = null ; |
04 | PreparedStatement ps = null ; |
06 | inputStream = new FileInputStream( "c:/tmp/a.txt" ); |
07 | wb = new XSSFWorkbook(inputStream); |
08 | con = ServiceLocator.getInstance().getDataSource( "jdbc/xxxDS" ).getConnection(); |
09 | ps = con.prepareStatement(sql); |
11 | } catch (Exception e) { |
12 | logger.error(e.getMessage()); |
15 | if (inputStream != null ){ |
18 | } catch (Exception e) { |
19 | logger.error( "Exception " +e.getMessage(),e); |
26 | } catch (IOException e) { |
27 | logger.error( "Exception " + e.getMessage(), e); |
33 | } catch (SQLException e) { |
34 | logger.error( "Exception " +e.getMessage(),e); |
40 | } catch (SQLException e) { |
41 | logger.error( "Exception " +e.getMessage(),e); |
声明的资源必须放在try语句块的外面,最后在finally中关闭。这里存在很多容易导致问题的地方:
1、声明的资源在try语句块中打开后,忘记在finally中关闭;
2、关闭资源的代码没有放在try-catch块中,一旦牵涉到多种资源的关闭,前面的抛异常,后面的被跳过,导致对应的关闭代码没有执行;
3、如果资源的关闭没有放在finally中,也会导致打开的资源没有正常关闭。
这些问题比较隐蔽,如果代码相对比较复杂,就非常容易被忽略。还有一个问题,就是代码很长,非常丑陋,非常不利于维护。当然,这个问题很容易想到解决方案,就是把资源关闭的代码抽出来,形成一个公共的工具方法,就像下面这样:
-
Java 代码01 | InputStream inputStream = null ; |
04 | PreparedStatement ps = null ; |
06 | inputStream = new FileInputStream( "c:/tmp/a.txt" ); |
07 | wb = new XSSFWorkbook(inputStream); |
08 | con = ServiceLocator.getInstance().getDataSource( "jdbc/xxxDS" ).getConnection(); |
09 | ps = con.prepareStatement(sql); |
11 | } catch (Exception e) { |
12 | logger.error(e.getMessage()); |
20 | } catch (SQLException e) { |
21 | logger.error( "Exception " + e.getMessage(), e); |
27 | } catch (SQLException e) { |
28 | logger.error( "Exception " + e.getMessage(), e); |
33 | public static void close(Closeable cloneable) { |
34 | if (cloneable != null ) { |
37 | } catch (Exception e) { |
38 | logger.error( "Exception " + e.getMessage(), e); |
由于java.sql.Connection,java.sql.PreparedStatement都没有实现java.io.Closeable接口,所以,这个公共的方法使用不了,当然,我们重新再定义带这两种参数的重载方法就行了,就像下面这样:
-
Java 代码01 | public static void close(Connection cloneable) { |
02 | if (cloneable != null ) { |
05 | } catch (Exception e) { |
06 | logger.error( "Exception " + e.getMessage(), e); |
11 | public static void close(PreparedStatement cloneable) { |
12 | if (cloneable != null ) { |
15 | } catch (Exception e) { |
16 | logger.error( "Exception " + e.getMessage(), e); |
最后,我们的代码像下面的样子:
-
Java 代码01 | InputStream inputStream = null ; |
04 | PreparedStatement ps = null ; |
06 | inputStream = new FileInputStream( "c:/tmp/a.txt" ); |
07 | wb = new XSSFWorkbook(inputStream); |
08 | con = ServiceLocator.getInstance().getDataSource( "jdbc/xxxDS" ).getConnection(); |
09 | ps = con.prepareStatement(sql); |
11 | } catch (Exception e) { |
12 | logger.error(e.getMessage()); |
OK,这已经比开始简化了不少,如果没有更进一步的追求,到此打住也无可厚非。但我们再仔细研究下就会发现依然存在以下问题:
1、待关闭的资源必须要在最外层声明,有多少种就要声明多少次,这和Java变量声明的原则(哪里用哪里声明)不一致;
2、声明多少次,就要调用多少次close,还是容易遗漏。
那最好的方案是什么呢?请看下面的代码:
-
Java 代码01 | MyCloser closer = MyCloser.create(); |
03 | InputStream inputStream = closer.register( new FileInputStream( "c:/tmp/a.txt" )); |
04 | Workbook wb = closer.register( new XSSFWorkbook(inputStream)); |
05 | DataSource dataSource = ServiceLocator.getInstance().getDataSource( "jdbc/xxxDS" ); |
06 | Connection con = closer.register(dataSource.getConnection()); |
07 | PreparedStatement ps = closer.register(con.prepareStatement(sql)); |
09 | } catch (Exception e) { |
10 | logger.error(e.getMessage()); |
引入MyCloser,变量用的时候再声明,不用放到最外面,资源的关闭,在finally中一行代码解决。下面是MyCloser的代码:
-
Java 代码001 | import java.io.Closeable; |
002 | import java.io.IOException; |
003 | import java.sql.Connection; |
004 | import java.sql.ResultSet; |
005 | import java.sql.SQLException; |
006 | import java.sql.Statement; |
008 | import com.google.common.io.Closer; |
011 | * com.google.common.io.Closer的扩展,对常见的非java.io.Closeable资源<br> |
012 | * 进行了适配,实现资源的注册和集中关闭,以简化客户端代码。 |
017 | public class MyCloser { |
018 | private final Closer closer; |
020 | private MyCloser(Closer closer) { |
021 | this .closer = closer; |
024 | public static MyCloser create(){ |
025 | return new MyCloser(Closer.create()); |
028 | public Connection register( final Connection connection) { |
029 | closer.register( new Closeable() { |
031 | public void close() throws IOException { |
034 | } catch (SQLException e) { |
035 | throw new IOException(e); |
042 | public <S extends Statement> S register( final S statement) { |
043 | closer.register( new Closeable() { |
045 | public void close() throws IOException { |
048 | } catch (SQLException e) { |
049 | throw new IOException(e); |
056 | public ResultSet register( final ResultSet resultSet){ |
057 | closer.register( new Closeable() { |
059 | public void close() throws IOException { |
062 | } catch (SQLException e) { |
063 | throw new IOException(e); |
070 | public void close() throws IOException { |
074 | public <C extends Closeable> C register(C closeable) { |
075 | return closer.register(closeable); |
078 | public RuntimeException rethrow(Throwable e) throws IOException { |
079 | return closer.rethrow(e); |
082 | public <X extends Exception> RuntimeException rethrow(Throwable e, Class<X> declaredType) throws IOException, X { |
083 | return closer.rethrow(e, declaredType); |
086 | public <X1 extends Exception, X2 extends Exception> RuntimeException rethrow(Throwable e, Class<X1> declaredType1, |
087 | Class<X2> declaredType2) throws IOException, X1, X2 { |
088 | return closer.rethrow(e, declaredType1, declaredType2); |
091 | public boolean equals(Object o) { |
092 | return closer.equals(o); |
095 | public int hashCode() { |
096 | return closer.hashCode(); |
099 | public String toString() { |
100 | return closer.toString(); |
这是一个典型的适配器模式,因为Google的Guava框架提供的Closer资源管理器只支持实现了java.io.Closeable的资源,对于像java.sql包中的资源,都没有实现该接口,因此,MyCloser提供了对应的适配,使所有的资源管理模式一致。在Java 7之前,这应该是最优雅的资源管理方案。
在Java 7及之后,我们可以使用最新的资源管理语法try-with-resource,上面的代码可以这么写:
-
Java 代码02 | InputStream inputStream = new FileInputStream( "c:/tmp/a.txt" ); |
03 | Workbook wb = new XSSFWorkbook(inputStream); |
04 | Connection con = ServiceLocator.getInstance().getDataSource( "jdbc/xxxDS" ).getConnection(); |
05 | PreparedStatement ps = con.prepareStatement(sql);){ |
08 | logger.error(e.getMessage()); |
可以看到,我们不用关心资源的关闭了,只要在try()中声明即可,这样的代码是最简洁也是最具表现力的,如果生产环境支持Java 7,最先考虑的应该是这个方案。
当然,由于历史原因,我们可能用到了一些第三方的包,牵涉到资源关闭,但对应的类却没有实现java.lang.AutoCloseable接口,当然也就不能使用try-with-resource语法来操作了,这时,有两种办法,一是提供一个适配的子类,实现java.lang.AutoCloseable接口,在其close中实现资源关闭逻辑,这样就能使用try-with-resource语法了;第二种就是使用MyCloser方案。推荐第一种,因为更简单。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
评论(0)