首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

告別樣板代碼:用 Notifier 類優雅實現 Event/Listener 模式

     編輯:Java 相關知識

痛點:手動實現監聽器的繁瑣儀式

在 Java 中實現觀察者模式(Event/Listener)時,開發者往往需要重複編寫:

  • 維護 List 集合(線程安全考量)
  • 實現 addListener / removeListener 方法
  • 遍歷列表調用回調,並處理單個監聽器異常不影響整體
  • 針對不同事件類型定義專用接口(ActionListener, ChangeListener...)

這不僅增加代碼體積,更分散核心業務邏輯。有沒有一個通用組件,能像發送廣播一樣簡單觸發事件?

方案:基於反射的通用 Notifier

核心思想:約定大於配置。監聽器只需實現特定名稱的方法(如 actionPerformed(Event)),Notifier 通過反射動態查找並調用,無需實現特定接口。

核心實現(增強版)

package com.generationjava.lang;

import java.util.*;
import java.lang.reflect.*;
import java.util.concurrent.*;

public class Notifier {
    // 使用 CopyOnWriteArrayList 保證線程安全,遍歷無需加鎖
    private final List listeners = new CopyOnWriteArrayList<>();
    private final String listenerMethod;
    private final Class[] eventParamTypes;

    /**
     * @param methodName 監聽器方法名,如 "actionPerformed"
     * @param eventType  事件參數類型,如 ActionEvent.class
     */
    public Notifier(String methodName, Class eventType) {
        this.listenerMethod = methodName;
        this.eventParamTypes = new Class[]{eventType};
    }

    public void addListener(Object listener) {
        if (listener == null) throw new NullPointerException("Listener cannot be null");
        listeners.add(listener);
    }

    public void removeListener(Object listener) {
        listeners.remove(listener);
    }

    public void notify(EventObject event) {
        if (event == null) throw new NullPointerException("Event cannot be null");
        
        // 快速失效:事件類型不匹配直接拋異常,而非靜默忽略
        if (!eventParamTypes[0].isInstance(event)) {
            throw new IllegalArgumentException("Event type mismatch: expected " + 
                eventParamTypes[0].getName() + ", got " + event.getClass().getName());
        }

        for (Object listener : listeners) {
            try {
                Method method = findListenerMethod(listener.getClass());
                method.invoke(listener, event);
            } catch (InvocationTargetException e) {
                // 解包真實業務異常,避免反射包裹層干擾排查
                Throwable cause = e.getCause();
                System.err.println("Listener " + listener + " failed: " + cause);
                cause.printStackTrace();
            } catch (Exception e) {
                System.err.println("Reflection error for listener " + listener + ": " + e);
            }
        }
    }

    // 緩存 Method 查找結果,提升性能
    private final ConcurrentMap, Method> methodCache = new ConcurrentHashMap<>();

    private Method findListenerMethod(Class listenerClass) {
        return methodCache.computeIfAbsent(listenerClass, cls -> {
            try {
                // 支持繼承鏈查找
                Method method = cls.getMethod(listenerMethod, eventParamTypes);
                // 允許非 public 方法(需設置 accessible)
                if (!method.isAccessible()) method.setAccessible(true);
                return method;
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException(
                    "Listener class " + cls.getName() + 
                    " must have method " + listenerMethod + "(" + eventParamTypes[0].getSimpleName() + ")"
                );
            }
        });
    }
}


使用示例:極簡調用

// 1. 定義事件源持有 Notifier
public class Button {
    private final Notifier notifier = new Notifier("actionPerformed", ActionEvent.class);
    
    public void addActionListener(Object listener) { notifier.addListener(listener); }
    public void removeActionListener(Object listener) { notifier.removeListener(listener); }
    
    public void click() {
        notifier.notify(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "click"));
    }
}

// 2. 監聽器無需實現任何接口,只需約定方法名
class MyHandler {
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button clicked: " + e.getSource());
    }
}

// 3. 組裝
Button btn = new Button();
btn.addActionListener(new MyHandler());
btn.click();  // 輸出: Button clicked: Button@...

進階優化與避坑指南

維度原版缺陷增強策略
線程安全 ArrayList 非線程安全,遍歷中增刪拋 ConcurrentModificationException 採用 CopyOnWriteArrayList,讀多寫少場景無鎖遍歷
反射性能 每次通知反射查找 Method,開銷大 ConcurrentHashMap 緩存 Method 對象,首次查找後 O(1) 獲取
異常處理 printStackTrace() 吞沒異常,難以排查 解包 InvocationTargetException,記錄真實業務異常堆棧;單個監聽器失敗不影響其他
類型安全 運行時才發現方法不存在/參數不匹配 構造器檢查事件類型;findListenerMethod 啟動時驗證監聽器合規性
內存洩漏 監聽器未移除導致無法 GC 建議搭配 WeakReference 實現 WeakNotifier 變體,或規範生命周期管理

適用邊界與替代方案

  • 適用: 輕量級 UI 組件、領域事件發布、插件擴展點、測試雙模擬事件總線。
  • 不適用: 高吞吐核心鏈路(反射開銷)、強類型契約要求場景、需異步/持久化事件總線場景。
  • 現代替代:
    • Guava EventBus(註解驅動、異步支持、死事件檢測)
    • Spring ApplicationEventPublisher(容器集成、事務綁定)
    • RxJava Subject/Processor(響應式流、背壓控制)

結語

Notifier 類以約 50 行代碼,通過反射機制消除了 Event/Listener 模式 80% 的樣板代碼。它展示了「約定優於配置」在 Java 元編程中的實踐價值。雖然生產級系統建議採用成熟事件總線框架,但理解並能手寫一個線程安全、高性能的 Notifier,是掌握 Java 反射與組件設計的極佳練習。下次當你第 N 次寫 for (Listener l : listeners) l.onEvent(e) 時,不妨引入這個小工具,讓代碼回歸業務本質。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved