清理多个不可关闭资源时减少嵌套

Posted

技术标签:

【中文标题】清理多个不可关闭资源时减少嵌套【英文标题】:Reduce nesting when cleaning up multiple non-Closeable resources 【发布时间】:2021-02-27 21:23:04 【问题描述】:

我有一个Closeable 需要在close() 方法中清理多个资源。每个资源都是我无法修改的final 类。包含的资源都不是CloseableAutoCloseable。我还需要致电super.close()。所以看来我无法使用try-with-resources 处理任何资源*。我当前的实现看起来像这样:

public void close() throws IOException 
    try 
        super.close();
     finally 
        try 
            container.shutdown();
         catch (final ShutdownException e) 
            throw new IOException("ShutdownException: ", e);
         finally 
            try 
                client.closeConnection();
             catch (final ConnectionException e) 
                throw new IOException("Handling ConnectionException: ", e);
            
        
    

我更喜欢嵌套较少的解决方案,但我不知道如何利用try-with-resources 或任何其他功能来做到这一点。 Code sandwiches 在这里似乎没有帮助,因为我根本没有使用资源,只是清理它们。由于资源不是Closeable,因此不清楚如何使用Java io ugly try-finally block 中推荐的解决方案。

* 即使super 类是Closeable,我也不能在try-with-resources 中使用super,因为super 只是语法糖,而不是真正的Java Object

【问题讨论】:

【参考方案1】:

对于try-with-resources,这是一个很好的(尽管是非正统的)案例。首先,您需要创建一些接口:

interface ContainerCleanup extends AutoCloseable 
    @Override
    void close() throws ShutdownException;

interface ClientCleanup extends AutoCloseable 
    @Override
    void close() throws ConnectionException;

如果这些接口仅在当前类中使用,我建议将它们设为内部接口。但是,如果您在多个类中使用它们,它们也可以用作公共实用程序接口。

然后在你的close() 方法中你可以这样做:

public void close() throws IOException 
    final Closeable ioCleanup = new Closeable() 
        @Override
        public void close() throws IOException 
            YourCloseable.super.close();
        
    ;
    final ContainerCleanup containerCleanup = new ContainerCleanup() 
        @Override
        public void close() throws ShutdownException 
            container.shutdown();
        
    ;
    final ClientCleanup clientCleanup = new ClientCleanup() 
        @Override
        public void close() throws ConnectionException 
            client.closeConnection();
        
    ;
    
    // Resources are closed in the reverse order in which they are declared,
    // so reverse the order of cleanup classes.
    // For more details, see Java Langauge Specification 14.20.3 try-with-resources:
    // https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20.3
    try (clientCleanup; containerCleanup; ioCleanup) 
        // try-with-resources only used to ensure that all resources are cleaned up.
     catch (final ShutdownException e) 
        throw new IOException("Handling ShutdownException: ", e);
     catch (final ConnectionException e) 
        throw new IOException("Handling ConnectionException: ", e);
    

当然,这在 Java 8 lambda 中变得更加优雅和简洁:

public void close() throws IOException 
    final Closeable ioCleanup = () -> super.close();
    final ContainerCleanup containerCleanup = () -> container.shutdown();
    final ClientCleanup clientCleanup = () -> client.closeConnection();
    
    // Resources are closed in the reverse order in which they are declared,
    // so reverse the order of cleanup classes.
    // For more details, see Java Langauge Specification 14.20.3 try-with-resources:
    // https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20.3
    try (clientCleanup; containerCleanup; ioCleanup) 
        // try-with-resources only used to ensure that all resources are cleaned up.
     catch (final ShutdownException e) 
        throw new IOException("Handling ShutdownException: ", e);
     catch (final ConnectionException e) 
        throw new IOException("Handling ConnectionException: ", e);
    

这消除了所有疯狂的嵌套,并具有保存被抑制异常的额外好处。在您的情况下,如果 client.closeConnection() 抛出,我们永远不会知道以前的方法是否抛出任何异常。所以堆栈跟踪看起来像这样:

Exception in thread "main" java.io.IOException: Handling ConnectionException: 
        at Main$YourCloseable.close(Main.java:69)
        at Main.main(Main.java:22)
Caused by: Main$ConnectionException: Failed to close connection.
        at Main$Client.closeConnection(Main.java:102)
        at Main$YourCloseable.close(Main.java:67)
        ... 1 more

通过使用try-with-resources,Java 编译器生成代码来处理被抑制的异常,因此我们将在堆栈跟踪中看到它们,如果我们愿意,我们甚至可以在调用代码中处理它们:

Exception in thread "main" java.io.IOException: Failed to close super.
        at Main$SuperCloseable.close(Main.java:104)
        at Main$YourCloseable.access$001(Main.java:35)
        at Main$YourCloseable $1.close(Main.java:49)
        at Main$YourCloseable.close(Main.java:68)
        at Main.main(Main.java:22)
        Suppressed: Main$ShutdownException: Failed to shut down container.
                at Main$Container.shutdown(Main.java:140)
                at Main$YourCloseable$2.close(Main.java:55)
                at Main$YourCloseable.close(Main.java:66)
                ... 1 more
        Suppressed: Main$ConnectionException: Failed to close connection.
                at Main$Client.closeConnection(Main.java:119)
                at Main$YourCloseable$3.close(Main.java:61)
                at Main$YourCloseable.close(Main.java:66)
                ... 1 more

注意事项

    如果清理的顺序很重要,您需要按照希望它们运行的​​反向 顺序声明资源清理类/lambda。我建议为此添加评论(就像我提供的那样)。

    如果有任何异常被抑制,则该异常的catch 块将执行。在这些情况下,最好更改 lambdas 来处理异常:

    final Closeable containerCleanup = () -> 
        try 
            container.shutdown();
         catch (final ShutdownException e) 
            // Handle shutdown exception
            throw new IOException("Handling shutdown exception:", e);
        
    
    

    在 lambda 中处理异常确实开始添加一些嵌套,但嵌套不像原来那样是递归的,所以它只会是一层深度。

即使有这些警告,我相信在自动抑制异常处理、简洁、优雅、可读性和减少嵌套(尤其是当您有 3 个或更多资源需要清理时)方面,优点远大于缺点。

【讨论】:

以上是关于清理多个不可关闭资源时减少嵌套的主要内容,如果未能解决你的问题,请参考以下文章

一键脚本清理DEBIAN系统无用组件 减少系统资源

boost::named_mutex: 最后一个进程关闭时安全清理

boost::named_mutex: 最后一个进程关闭时安全清理

REST 复杂/复合/嵌套资源 [关闭]

REST 复杂/复合/嵌套资源 [关闭]

杀死风暴拓扑后资源清理