前言
Github:https://github.com/yihonglei/daisy-framework
一 事件監聽
Java很多web框架源碼都能看到很多事件監聽的存在,主要解決的問題就是類似觀察者模式的行爲,
當一件事情發生的時候,其他的相關事情需要知道並處理。
事件監聽三要素: 事件源,事件對象,事件監聽器。
1、監聽器需要註冊,即註冊到某個數據結構,map或list等等存儲起來;
2、一件事情的發生源頭,這件事情需要觸發監聽器,讓監聽器能感受到,監聽器才能去處理業務;
3、調用每一個監聽器,執行相關處理;
要理解監聽器,需要理解幾個問題,監聽器存儲在什麼數據結構?監聽器如何被觸發?監聽器如何處理業務?
上一個代碼吧,要不然太蒙了!
二 實例代碼
demo基於spring boot環境。
1、自定義事件
Java自定義事件實現EventObject對象。
package com.jpeony.common.event;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.EventObject;
/**
* 自定義事件
*
* @author yihonglei
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class DaisyEvent extends EventObject {
DaisyEventEnum event;
Object[] args;
/**
* 事件對象
*
* @param daisyEventEnum 事件類型
* @param source 觸發事件初始化對象
* @param args 其他參數
* @author yihonglei
*/
public DaisyEvent(DaisyEventEnum daisyEventEnum, Object source, Object... args) {
super(source);
this.event = daisyEventEnum;
this.args = args;
}
/**
* 事件對象
*
* @param daisyEventEnum 事件
* @param source 觸發事件初始化對象
* @author yihonglei
*/
public DaisyEvent(DaisyEventEnum daisyEventEnum, Object source) {
this(daisyEventEnum, source, null);
}
}
DaisyEventEnum事件類型,args事件參數。
2、定義事件類型
定義事件類型主要是對可以針對事件類型進行發佈。
package com.jpeony.common.event;
/**
* 事件枚舉
*
* @author yihonglei
*/
public enum DaisyEventEnum {
/**
* 初始化
*/
INIT_EVENT,
/**
* 測試事件
*/
TEST_EVENT;
}
3、事件監聽器
事件監聽器繼承EventListener,定義事件發佈的標準方法。
package com.jpeony.common.event;
import java.util.EventListener;
/**
* 自定義事件監聽,Spring的ApplicationListener也是這麼幹的,自定義方便自己對事件進行管理。
* 注意,這裏的事件是同步的,如果有需要,可以將事件提交到線程池異步化執行或者使用Spring的異步事件方式,
* 它也是提交到線程池實現異步化。
*
* @author yihonglei
*/
public interface DaisyEventListener extends EventListener {
/**
* 觸發事件
*
* @param e 相關事件
*/
void fire(DaisyEvent e);
}
event1
package com.jpeony.core.service.event;
import com.jpeony.common.event.DaisyEvent;
import com.jpeony.common.event.DaisyEventEnum;
import com.jpeony.common.event.DaisyEventListener;
import com.jpeony.common.event.DaisyEventManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 實現daisyEventListener接口,在構造方法添加你的事件即可!
*
* @author yihonglei
*/
@Slf4j
@Component
public class Test1EventListener implements DaisyEventListener {
public Test1EventListener() {
DaisyEventManager.addListener(DaisyEventEnum.TEST_EVENT, this);
}
@Override
public void fire(DaisyEvent e) {
try {
String userName = (String) e.getSource();
log.info("【Test1】對發佈事件,事件觸發,事件參數測試!userName={}", userName);
} catch (Exception ex) {
log.error("出異常了!", ex);
}
}
}
event2
package com.jpeony.core.service.event;
import com.jpeony.common.event.DaisyEvent;
import com.jpeony.common.event.DaisyEventEnum;
import com.jpeony.common.event.DaisyEventListener;
import com.jpeony.common.event.DaisyEventManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 實現daisyEventListener接口,在構造方法添加你的事件即可!
*
* @author yihonglei
*/
@Slf4j
@Component
public class Test2EventListener implements DaisyEventListener {
public Test2EventListener() {
DaisyEventManager.addListener(DaisyEventEnum.TEST_EVENT, this);
}
@Override
public void fire(DaisyEvent e) {
try {
String userName = (String) e.getSource();
log.info("【Test2】對發佈事件,事件觸發,事件參數測試!userName={}", userName);
} catch (Exception ex) {
log.error("出異常了!", ex);
}
}
}
spring boot啓動時註冊到時間管理器DaisyEventManager(下面有源碼)。
4、事件管理器
負責事件的註冊,事件觸發。
package com.jpeony.common.event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.EnumMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 事件管理器
*
* @author yihonglei
*/
public class DaisyEventManager {
private static Logger logger = LoggerFactory.getLogger(DaisyEventManager.class);
private static EnumMap<DaisyEventEnum, CopyOnWriteArrayList<DaisyEventListener>> eventListeners = new EnumMap<>(DaisyEventEnum.class);
public static void addListener(DaisyEventEnum event, DaisyEventListener el) {
CopyOnWriteArrayList<DaisyEventListener> listeners = eventListeners.get(event);
if (listeners == null) {
listeners = new CopyOnWriteArrayList<>();
eventListeners.put(event, listeners);
} else {
for (DaisyEventListener listener : listeners) {
if (listener.getClass() == el.getClass()) {
logger.info("more than one instances added, this class is {}", listener.getClass());
return;
}
}
}
listeners.add(el);
}
/**
* 移出指定事件的指定監聽器
*
* @author yihonglei
*/
public static synchronized void removeListener(DaisyEventEnum event, DaisyEventListener el) {
CopyOnWriteArrayList<DaisyEventListener> listeners = eventListeners.get(event);
if (listeners != null) {
listeners.remove(el);
}
}
/**
* 清除指定事件的所有監聽器
*
* @author yihonglei
*/
public static void purgeListeners(DaisyEventEnum event) {
CopyOnWriteArrayList<DaisyEventListener> listeners = eventListeners.get(event);
if (listeners != null) {
listeners.clear();
// 移除該事件
eventListeners.remove(event);
}
}
/**
* 觸發事件
*
* @author yihonglei
*/
public static void fireEvent(DaisyEvent obj) {
CopyOnWriteArrayList<DaisyEventListener> listeners = eventListeners.get(obj.getEvent());
if (listeners == null) {
return;
}
for (DaisyEventListener listener : listeners) {
listener.fire(obj);
}
}
}
spring boot啓動的時候,Test1EventListener和Test2EventListener通過構造器的事件管理器註冊到EnumMap。
DaisyEventManager.addListener(DaisyEventEnum.TEST_EVENT, this);
EnumMap是一個事件類型對應多個listener的數據結構,Map的key是事件類型,value是監聽的list集合。
5、測試
package com.jpeony.test.service.event;
import com.jpeony.common.event.DaisyEvent;
import com.jpeony.common.event.DaisyEventEnum;
import com.jpeony.common.event.DaisyEventManager;
import com.jpeony.test.BaseServletTest;
import org.junit.Test;
/**
* @author yihonglei
*/
public class EventTest extends BaseServletTest {
@Test
public void test() {
// 事件發佈,支持任意參數,可以是一個值,也可以傳入對象
String userName = "Tom";
DaisyEventManager.fireEvent(new DaisyEvent(DaisyEventEnum.TEST_EVENT, userName));
}
}
發佈TEST_EVENT事件,event1和event2監聽並執行。
DaisyEventManager.fireEvent(new DaisyEvent(DaisyEventEnum.TEST_EVENT, userName));
傳入TEST_EVENT,事件管理器根據key爲TEST_EVENT取出對應的監聽器列表並循環執行。
6、總結
1)監聽事件繼承EventObject;
2)監聽器繼承於EventLisneter,也可以不繼承,這個接口只是一個標記接口,然後定義統一的事件發佈方法;
3)弄一個事件管理器,不弄也行,我這裏是爲了方便,你可以在某個類裏面搞一個Map或list存這些監聽器,
數據結構選取可以根據自己的業務來,總之,要把這些監聽器對象存起來;
4)事件執行就比較簡單,就是根據事件類型取出相應的監聽器,循環執行,完事!
三 Tomcat的事件監聽
最近無聊看了下tomcat源碼,單獨簡單聊下tomcat裏的事件監聽怎麼實現的。
1、事件定義
你會發現,tomcat生命週期管理的自定義事件也只是繼承了EventObject,data事件監聽器處理需要的參數,
type就是事件類型。
2、監聽器定義
剛纔我自己實現監聽器,標識接口用的Java的EventListener,這個是隻是個標記接口,
表明跟我這個EventListener相關的都是要實現監聽器,你別的不相關的別瞎搞,只是一個規範而已。
tomcat裏面就只是自己定義了事件發佈標準,監聽器都走LifecycleListener接口標識。
3、監聽註冊
數據結構
最後都是調用add方法,把監聽都放在這個list裏面。
4、監聽觸發
這個地方也是通過type取到監聽list,然後循環觸發執行。
四 事件監聽本質思想
1、事件自定義,繼承EventObject;
2、監聽器定義,要統一標識接口和發佈方法;
3、監聽器註冊,搞一個合適的數據結構把你的監聽分類存起來;
4、事件觸發,根據事件類型,從數據結構取出相應監聽器,循環執行;