在 Java 程式設計中,事件處理機制 不僅支撐 AWT/Swing/JavaFX 的圖形介面互動,更是 JavaBean 組件模型、Spring ApplicationEvent、乃至現代反應式流 (Reactive Streams) 的核心基礎。掌握其運作原理,有助於設計鬆散耦合、可擴展的系統架構。
java.util.EventObject,封裝事件發生時的上下文資訊 (來源、屬性值等)。java.util.EventListener,定義回調方法簽名 (如 propertyChanged(PropertyEvent e))。public class PropertyEvent extends EventObject {
private final String propertyName;
private final Object oldValue, newValue;
public PropertyEvent(Object source, String propertyName, Object oldValue, Object newValue) {
super(source);
this.propertyName = propertyName;
this.oldValue = oldValue;
this.newValue = newValue;
}
// getter...
}
public interface PropertyListener extends EventListener {
void propertyChanged(PropertyEvent event);
}
public class Exam {
private int property;
private final Set<PropertyListener> listeners = Collections.synchronizedSet(new HashSet<>());
public void addPropertyListener(PropertyListener l) { listeners.add(l); }
public void removePropertyListener(PropertyListener l) { listeners.remove(l); }
public void setProperty(int newValue) {
int old = this.property;
this.property = newValue;
firePropertyChange(new PropertyEvent(this, "property", old, newValue));
}
private void firePropertyChange(PropertyEvent event) {
// 複製快照避免迭代時併發修改
List<PropertyListener> snapshot;
synchronized (listeners) { snapshot = new ArrayList<>(listeners); }
for (PropertyListener l : snapshot) {
l.propertyChanged(event);
}
}
}
Exam exam = new Exam();
// 1. 實現介面的類作為監聽器
exam.addPropertyListener(this); // this 實現 PropertyListener
// 2. 多個獨立監聽器
exam.addPropertyListener(listener1);
exam.addPropertyListener(listener2);
// 3. 匿名內部類 / Lambda (Java 8+)
exam.addPropertyListener(event -> {
System.out.println("屬性 " + event.getPropertyName() + " 從 " + event.getOldValue() + " 變為 " + event.getNewValue());
});
上述結構正是 Observer Pattern 的標準實現:
ApplicationEvent (Spring) 或 AWTEvent (AWT) 提供 source、timestamp、id 等共同欄位。OrderCreatedEvent 包含 orderId、customerId)。fireXxx 直接在呼叫執行緒逐個回調,簡單但可能阻塞來源。ExecutorService 或使用 EventBus (Guava/Spring) 解耦執行緒,提升響應性。// 使用 WeakReference 包裝,配合 ReferenceQueue 定期清理
private final Set<WeakReference<PropertyListener>> listeners = ConcurrentHashMap.newKeySet();
避免監聽器持有對事件源的強引用導致無法 GC (常見於靜態事件總線)。
addXxxListener / removeXxxListenerXxxEventXxxListenerfireXxxEvent (protected/package-private)遵循上述慣例的類,可被 Introspector 自動識別為 JavaBean,支援可視化建模工具、序列化、Spring 容器自動裝配等。
| 錯誤 | 後果 | 修正 |
|---|---|---|
| 在 fire 方法中直接迭代原集合 | 監聽器內部移除自身導致 ConcurrentModificationException | 先複製快照再迭代 |
| 事件物件可變且被多執行緒共用 | 競態條件導致資料不一致 | 事件物件設計為不可變 |
| 未提供 removeListener | 無法動態解除訂閱,記憶體洩漏 | 成對提供 add/remove |
| 監聽器回調拋出未檢查異常中斷後續通知 | 部分監聽器收不到事件 | 捕獲並記錄異常,繼續通知其餘監聽器 |
Java 事件處理機制以 EventObject + EventListener + 事件源 三元組為核心,實現了經典觀察者模式的標準化、類型安全版本。無論是 Swing GUI、JavaBean 組件、Spring 事件總線,還是領域驅動設計中的領域事件,皆遵循此範式。深入理解其線程安全、記憶體管理、同步/非同步擴展細節,能幫助開發者構建更健壯、可維護的事件驅動系統。