Java中如何安全使用Cleaner注销监听器且不破坏类封装?
解决方案
核心思路是打破强引用循环,同时不破坏类的封装性,具体调整如下:
步骤1:调整Listener实现,切断对Action的强引用
系统剪贴板是全局长生命周期对象,会一直强引用注册的FlavorListener,如果Listener持有Action的强引用会导致Action永远无法被GC,所以将Listener改为静态内部类+弱引用持有Action实例:
public class PasteAction extends FocusedComponentAction { // 静态内部类不默认持有外部类引用,用弱引用持有Action避免阻止GC private static final class PasteFlavorListener implements FlavorListener { private final WeakReference<PasteAction> actionRef; private PasteFlavorListener(PasteAction action) { this.actionRef = new WeakReference<>(action); } @Override public void flavorsChanged(FlavorEvent event) { PasteAction action = actionRef.get(); if (action != null) { action.updateEnabled(); } } } private final FlavorListener listener = new PasteFlavorListener(this); // 持有清理令牌避免被提前回收 @SuppressWarnings("unused") private final FinalizeGuardian finalizeGuardian; public PasteAction() { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.addFlavorListener(listener); // 注册清理逻辑,lambda仅持有listener,不持有PasteAction实例 finalizeGuardian = FinalizeGuardian.register(() -> { Clipboard sysClipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); sysClipboard.removeFlavorListener(listener); }); } // 原有业务逻辑保持不变 @Override protected boolean shouldBeEnabled(@Nonnull JComponent focusOwner) { TransferHandler transferHandler = focusOwner.getTransferHandler(); if (transferHandler == null) { return false; } Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); DataFlavor[] flavorsInClipboard = clipboard.getAvailableDataFlavors(); return transferHandler.canImport(focusOwner, flavorsInClipboard); } @Override protected void doAction(@Nonnull JComponent focusOwner) throws Exception { Action action = TransferHandler.getPasteAction(); action.actionPerformed(new ActionEvent( focusOwner, ActionEvent.ACTION_PERFORMED, (String) action.getValue(Action.NAME))); } }
步骤2:重写FinalizeGuardian适配Cleaner逻辑
package org.trypticon.hex.gui.util; import java.lang.ref.Cleaner; public final class FinalizeGuardian { private static final Cleaner CLEANER = Cleaner.create(); public static FinalizeGuardian register(Runnable cleanupLogic) { FinalizeGuardian guardian = new FinalizeGuardian(); // 仅将guardian本身注册到Cleaner,清理逻辑和Action无关联 CLEANER.register(guardian, cleanupLogic); return guardian; } private FinalizeGuardian() {} }
实现原理
- 静态内部类+弱引用的Listener,既可以正常回调Action的方法,又不会阻止Action被GC回收
- Cleaner的清理逻辑仅持有Listener实例,不持有Action实例,不会形成强引用循环
- 当Action没有其他强引用时会被GC标记,其持有的FinalizeGuardian也会变为不可达,触发Cleaner执行清理逻辑,自动从剪贴板移除Listener,避免内存泄漏
- 所有调整都在PasteAction内部完成,Listener仍为私有内部类,完全没有破坏原有封装性
内容的提问来源于stack exchange,提问作者Hakanai
相关产品推荐
相关产品推荐

