清理多个不可关闭资源时减少嵌套
Posted
技术标签:
【中文标题】清理多个不可关闭资源时减少嵌套【英文标题】:Reduce nesting when cleaning up multiple non-Closeable resources 【发布时间】:2021-02-27 21:23:04 【问题描述】:我有一个Closeable
需要在close()
方法中清理多个资源。每个资源都是我无法修改的final
类。包含的资源都不是Closeable
或AutoCloseable
。我还需要致电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 个或更多资源需要清理时)方面,优点远大于缺点。
【讨论】:
以上是关于清理多个不可关闭资源时减少嵌套的主要内容,如果未能解决你的问题,请参考以下文章
boost::named_mutex: 最后一个进程关闭时安全清理