You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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() {}
}

实现原理

  1. 静态内部类+弱引用的Listener,既可以正常回调Action的方法,又不会阻止Action被GC回收
  2. Cleaner的清理逻辑仅持有Listener实例,不持有Action实例,不会形成强引用循环
  3. 当Action没有其他强引用时会被GC标记,其持有的FinalizeGuardian也会变为不可达,触发Cleaner执行清理逻辑,自动从剪贴板移除Listener,避免内存泄漏
  4. 所有调整都在PasteAction内部完成,Listener仍为私有内部类,完全没有破坏原有封装性

内容的提问来源于stack exchange,提问作者Hakanai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 07:06:01