如何在Eclipse中实现类JSR 305 @WillClose的资源关闭注解?
核心需求
告知Eclipse:自定义辅助方法closedown(AutoCloseable... aaa)会关闭传入的所有AutoCloseable对象,消除该方法调用后出现的虚假“潜在资源泄漏”提示。
背景说明
接手一套大型Java应用,其中DAO类代码质量较差:大量方法重复使用PreparedStatement、ResultSet等资源,有时未在重用前关闭,导致Eclipse生成数百条“资源泄漏:此处未关闭rs/ps”警告。
为简化资源清理流程,编写了closedown方法,对每个传入的AutoCloseable对象执行if (a!=null) a.close();逻辑。但Eclipse无法通过源码分析判断该方法已正确关闭资源,因此又生成大量虚假的“潜在资源泄漏:ps/rs可能未在此处关闭”提示。
当前应用运行在Java 7(正迁移至Java 8),try-with-resources语法仅适用于Connection对象(因Statement、ResultSet被大量重复使用),且不想使用@SuppressWarnings注解抑制提示,希望通过类似JSR 305中@WillClose的注解机制,让Eclipse识别资源已被关闭。
解决方案:自定义注解+Eclipse配置
Eclipse支持通过自定义注解标记“会关闭参数资源”的方法,具体步骤如下:
1. 创建@WillClose自定义注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface WillClose { }
2. 修改closedown方法,添加注解
在方法的可变参数上标记@WillClose,告知Eclipse该参数对应的资源会被方法关闭:
package misc.util; public class Closer { public static void closedown(final @WillClose AutoCloseable... objects) { if (objects != null) { for (AutoCloseable o : objects) { try { if (o != null) { o.close(); } } catch (Exception e) { System.err.println("Unexpected error when trying to close object: '" + o.toString() + "'."); } } } } public static java.sql.Connection getConnection() { return null; } }
3. 配置Eclipse识别该注解
- 打开Eclipse偏好设置:
Window > Preferences > Java > Compiler > Errors/Warnings > Potential programming problems - 找到Resource leak区域,点击
Configure annotations...按钮 - 在弹出的窗口中,切换到Closing annotations标签页,点击
Add... - 输入自定义
@WillClose注解的全类名(例如misc.util.WillClose),点击确定保存配置
完成后,Eclipse会识别closedown方法已关闭传入的资源,原有的虚假“潜在资源泄漏”提示会消失,而真实的资源泄漏(如示例中select 'b'行的提示)仍会保留。
最小复现示例
工具类Closer(修改前)
package misc.util; public class Closer { public static void closedown(final AutoCloseable... objects) { if (objects != null) { for (AutoCloseable o : objects) { try { if (o != null) { o.close(); } } catch (Exception e) { System.err.println("Unexpected error when trying to close object: '" + o.toString() + "'."); } } } } public static java.sql.Connection getConnection() { return null; } }
示例类Example
package misc.demo; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import static misc.util.Closer.closedown; import static misc.util.Closer.getConnection; public class Example { public static void main(String[] args) { PreparedStatement ps = null; ResultSet rs = null; try (Connection conn = getConnection();) { ps = conn.prepareStatement("select 'a' from dual"); rs = ps.executeQuery(); ps = conn.prepareStatement("select 'b' from dual"); // 真实资源泄漏警告:"Resource leak: 'ps' is not closed at this location" rs = ps.executeQuery(); closedown(rs, ps); ps = conn.prepareStatement("select 'c' from dual"); // 虚假提示:"Potential resource leak: 'ps' may not be closed at this location" rs = ps.executeQuery(); // 虚假提示:"Potential resource leak: 'rs' may not be closed at this location" if (rs != null) { rs.close(); } if (ps != null) { ps.close(); } ps = conn.prepareStatement("select 'd' from dual"); // Eclipse识别资源已关闭,无提示 rs = ps.executeQuery(); // Eclipse识别资源已关闭,无提示 } catch (Exception e) { e.printStackTrace(); } finally { closedown(rs, ps); } } }
内容的提问来源于stack exchange,提问作者Miguel Farah

