作者:小傅哥
部落格:https://bugstack.cn
沉澱、分享、成長,讓自己和他人都能有所收穫!?
一、前言
能解耦,是多麼重要的一件事情!
摔杯為號、看我眼色行事、見南面火起,這是在嘎哈麼?這其實是在通過事物傳播進行解耦引線和炸彈,僅僅是這樣的一個解耦,它放到了多少村夫莽漢,劫了法場,篡了兵權!
這樣的解耦場景在網際網路開發的設計中使用的也是非常頻繁,如:這裡需要一個註冊完成事件推送訊息
、使用者下單我會傳送一個MQ
、收到我的支付訊息就可以發貨了
等等,都是依靠事件訂閱和釋出以及MQ訊息這樣的元件,來處理系統之間的呼叫解耦,最終通過解耦的方式來提升整體系統架構的負載能力。
其實解耦思路可以理解為設計模式中觀察者模式的具體使用效果,在觀察者模式中當物件間存在一對多關係時,則使用觀察者模式,它是一種定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。這讓我想起了我每個月的車牌搖號,都會推送給我一條本月沒中籤的訊息!!!
二、目標
在 Spring 中有一個 Event 事件功能,它可以提供事件的定義、釋出以及監聽事件來完成一些自定義的動作。比如你可以定義一個新使用者註冊的事件,當有使用者執行註冊完成後,在事件監聽中給使用者傳送一些優惠券和簡訊提醒,這樣的操作就可以把屬於基本功能的註冊和對應的策略服務分開,降低系統的耦合。以後在擴充套件註冊服務,比如需要新增風控策略、新增實名認證、判斷使用者屬性等都不會影響到依賴註冊成功後執行的動作。
那麼在本章節我們需要以觀察者模式的方式,設計和實現 Spring Event 的容器事件和事件監聽器功能,最終可以讓我們在現又實現的 Spring 框架中可以定義、監聽和釋出自己的事件資訊。
三、方案
其實事件的設計本身就是一種觀察者模式的實現,它所要解決的就是一個物件狀態改變給其他物件通知的問題,而且要考慮到易用和低耦合,保證高度的協作。
在功能實現上我們需要定義出事件類、事件監聽、事件釋出,而這些類的功能需要結合到 Spring 的 AbstractApplicationContext#refresh(),以便於處理事件初始化和註冊事件監聽器的操作。整體設計結構如下圖:
- 在整個功能實現過程中,仍然需要在面向使用者的應用上下文
AbstractApplicationContext
中新增相關事件內容,包括:初始化事件釋出者、註冊事件監聽器、釋出容器重新整理完成事件。 - 使用觀察者模式定義事件類、監聽類、釋出類,同時還需要完成一個廣播器的功能,接收到事件推送時進行分析處理符合監聽事件接受者感興趣的事件,也就是使用 isAssignableFrom 進行判斷。
- isAssignableFrom 和 instanceof 相似,不過 isAssignableFrom 是用來判斷子類和父類的關係的,或者介面的實現類和介面的關係的,預設所有的類的終極父類都是Object。如果A.isAssignableFrom(B)結果是true,證明B可以轉換成為A,也就是A可以由B轉換而來。
四、實現
1. 工程結構
small-spring-step-10
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── FactoryBeanRegistrySupport.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── FactoryBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── event
│ │ │ ├── AbstractApplicationEventMulticaster.java
│ │ │ ├── ApplicationContextEvent.java
│ │ │ ├── ApplicationEventMulticaster.java
│ │ │ ├── ContextClosedEvent.java
│ │ │ ├── ContextRefreshedEvent.java
│ │ │ └── SimpleApplicationEventMulticaster.java
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ ├── ApplicationEvent.java
│ │ ├── ApplicationEventPublisher.java
│ │ ├── ApplicationListener.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── event
│ ├── ContextClosedEventListener.java
│ ├── ContextRefreshedEventListener.java
│ ├── CustomEvent.java
│ └── CustomEventListener.java
└── ApiTest.java
工程原始碼:公眾號「bugstack蟲洞棧」,回覆:Spring 專欄,獲取完整原始碼
容器事件和事件監聽器實現類關係,如圖 11-2
- 以上整個類關係圖以圍繞實現 event 事件定義、釋出、監聽功能實現和把事件的相關內容使用 AbstractApplicationContext#refresh 進行註冊和處理操作。
- 在實現的過程中主要以擴充套件 spring context 包為主,事件的實現也是在這個包下進行擴充套件的,當然也可以看出來目前所有的實現內容,仍然是以IOC為主。
- ApplicationContext 容器繼承事件釋出功能介面 ApplicationEventPublisher,並在實現類中提供事件監聽功能。
- ApplicationEventMulticaster 介面是註冊監聽器和釋出事件的廣播器,提供新增、移除和釋出事件方法。
- 最後是釋出容器關閉事件,這個仍然需要擴充套件到 AbstractApplicationContext#close 方法中,由註冊到虛擬機器的鉤子實現。
2. 定義和實現事件
cn.bugstack.springframework.context.ApplicationEvent
public abstract class ApplicationEvent extends EventObject {
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public ApplicationEvent(Object source) {
super(source);
}
}
- 以繼承 java.util.EventObject 定義出具備事件功能的抽象類 ApplicationEvent,後續所有事件的類都需要繼承這個類。
cn.bugstack.springframework.context.event.ApplicationContextEvent
public class ApplicationContextEvent extends ApplicationEvent {
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public ApplicationContextEvent(Object source) {
super(source);
}
/**
* Get the <code>ApplicationContext</code> that the event was raised for.
*/
public final ApplicationContext getApplicationContext() {
return (ApplicationContext) getSource();
}
}
cn.bugstack.springframework.context.event.ContextClosedEvent
public class ContextClosedEvent extends ApplicationContextEvent{
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public ContextClosedEvent(Object source) {
super(source);
}
}
cn.bugstack.springframework.context.event.ContextRefreshedEvent
public class ContextRefreshedEvent extends ApplicationContextEvent{
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public ContextRefreshedEvent(Object source) {
super(source);
}
}
- ApplicationContextEvent 是定義事件的抽象類,所有的事件包括關閉、重新整理,以及使用者自己實現的事件,都需要繼承這個類。
- ContextClosedEvent、ContextRefreshedEvent,分別是 Spring 框架自己實現的兩個事件類,可以用於監聽重新整理和關閉動作。
3. 事件廣播器
cn.bugstack.springframework.context.event.ApplicationEventMulticaster
public interface ApplicationEventMulticaster {
/**
* Add a listener to be notified of all events.
* @param listener the listener to add
*/
void addApplicationListener(ApplicationListener<?> listener);
/**
* Remove a listener from the notification list.
* @param listener the listener to remove
*/
void removeApplicationListener(ApplicationListener<?> listener);
/**
* Multicast the given application event to appropriate listeners.
* @param event the event to multicast
*/
void multicastEvent(ApplicationEvent event);
}
- 在事件廣播器中定義了新增監聽和刪除監聽的方法以及一個廣播事件的方法
multicastEvent
最終推送時間訊息也會經過這個介面方法來處理誰該接收事件。
cn.bugstack.springframework.context.event.AbstractApplicationEventMulticaster
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware {
public final Set<ApplicationListener<ApplicationEvent>> applicationListeners = new LinkedHashSet<>();
private BeanFactory beanFactory;
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
applicationListeners.add((ApplicationListener<ApplicationEvent>) listener);
}
@Override
public void removeApplicationListener(ApplicationListener<?> listener) {
applicationListeners.remove(listener);
}
@Override
public final void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
protected Collection<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
LinkedList<ApplicationListener> allListeners = new LinkedList<ApplicationListener>();
for (ApplicationListener<ApplicationEvent> listener : applicationListeners) {
if (supportsEvent(listener, event)) allListeners.add(listener);
}
return allListeners;
}
/**
* 監聽器是否對該事件感興趣
*/
protected boolean supportsEvent(ApplicationListener<ApplicationEvent> applicationListener, ApplicationEvent event) {
Class<? extends ApplicationListener> listenerClass = applicationListener.getClass();
// 按照 CglibSubclassingInstantiationStrategy、SimpleInstantiationStrategy 不同的例項化型別,需要判斷後獲取目標 class
Class<?> targetClass = ClassUtils.isCglibProxyClass(listenerClass) ? listenerClass.getSuperclass() : listenerClass;
Type genericInterface = targetClass.getGenericInterfaces()[0];
Type actualTypeArgument = ((ParameterizedType) genericInterface).getActualTypeArguments()[0];
String className = actualTypeArgument.getTypeName();
Class<?> eventClassName;
try {
eventClassName = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new BeansException("wrong event class name: " + className);
}
// 判定此 eventClassName 物件所表示的類或介面與指定的 event.getClass() 引數所表示的類或介面是否相同,或是否是其超類或超介面。
// isAssignableFrom是用來判斷子類和父類的關係的,或者介面的實現類和介面的關係的,預設所有的類的終極父類都是Object。如果A.isAssignableFrom(B)結果是true,證明B可以轉換成為A,也就是A可以由B轉換而來。
return eventClassName.isAssignableFrom(event.getClass());
}
}
- AbstractApplicationEventMulticaster 是對事件廣播器的公用方法提取,在這個類中可以實現一些基本功能,避免所有直接實現介面放還需要處理細節。
- 除了像 addApplicationListener、removeApplicationListener,這樣的通用方法,這裡這個類中主要是對 getApplicationListeners 和 supportsEvent 的處理。
- getApplicationListeners 方法主要是摘取符合廣播事件中的監聽處理器,具體過濾動作在 supportsEvent 方法中。
- 在 supportsEvent 方法中,主要包括對Cglib、Simple不同例項化需要獲取目標Class,Cglib代理類需要獲取父類的Class,普通例項化的不需要。接下來就是通過提取介面和對應的 ParameterizedType 和 eventClassName,方便最後確認是否為子類和父類的關係,以此證明此事件歸這個符合的類處理。可以參考程式碼中的註釋
supportsEvent 方法執行截圖
- 在程式碼除錯中可以看到,最終 eventClassName 和 event.getClass() 在 isAssignableFrom 判斷下為 true
- 關於 CglibSubclassingInstantiationStrategy、SimpleInstantiationStrategy 可以嘗試在 AbstractApplicationContext 類中更換驗證。
4. 事件釋出者的定義和實現
cn.bugstack.springframework.context.ApplicationEventPublisher
public interface ApplicationEventPublisher {
/**
* Notify all listeners registered with this application of an application
* event. Events may be framework events (such as RequestHandledEvent)
* or application-specific events.
* @param event the event to publish
*/
void publishEvent(ApplicationEvent event);
}
- ApplicationEventPublisher 是整個一個事件的釋出介面,所有的事件都需要從這個介面釋出出去。
cn.bugstack.springframework.context.support.AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
private ApplicationEventMulticaster applicationEventMulticaster;
@Override
public void refresh() throws BeansException {
// 6. 初始化事件釋出者
initApplicationEventMulticaster();
// 7. 註冊事件監聽器
registerListeners();
// 9. 釋出容器重新整理完成事件
finishRefresh();
}
private void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, applicationEventMulticaster);
}
private void registerListeners() {
Collection<ApplicationListener> applicationListeners = getBeansOfType(ApplicationListener.class).values();
for (ApplicationListener listener : applicationListeners) {
applicationEventMulticaster.addApplicationListener(listener);
}
}
private void finishRefresh() {
publishEvent(new ContextRefreshedEvent(this));
}
@Override
public void publishEvent(ApplicationEvent event) {
applicationEventMulticaster.multicastEvent(event);
}
@Override
public void close() {
// 釋出容器關閉事件
publishEvent(new ContextClosedEvent(this));
// 執行銷燬單例bean的銷燬方法
getBeanFactory().destroySingletons();
}
}
- 在抽象應用上下文 AbstractApplicationContext#refresh 中,主要新增了
初始化事件釋出者
、註冊事件監聽器
、釋出容器重新整理完成事件
,三個方法用於處理事件操作。 - 初始化事件釋出者(initApplicationEventMulticaster),主要用於例項化一個 SimpleApplicationEventMulticaster,這是一個事件廣播器。
- 註冊事件監聽器(registerListeners),通過 getBeansOfType 方法獲取到所有從 spring.xml 中載入到的事件配置 Bean 物件。
- 釋出容器重新整理完成事件(finishRefresh),釋出了第一個伺服器啟動完成後的事件,這個事件通過 publishEvent 釋出出去,其實也就是呼叫了 applicationEventMulticaster.multicastEvent(event); 方法。
- 最後是一個 close 方法中,新增加了釋出一個容器關閉事件。
publishEvent(new ContextClosedEvent(this));
五、測試
1. 建立一個事件和監聽器
cn.bugstack.springframework.test.event.CustomEvent
public class CustomEvent extends ApplicationContextEvent {
private Long id;
private String message;
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public CustomEvent(Object source, Long id, String message) {
super(source);
this.id = id;
this.message = message;
}
// ...get/set
}
- 建立一個自定義事件,在事件類的建構函式中可以新增自己的想要的入參資訊。這個事件類最終會被完成的拿到監聽裡,所以你新增的屬性都會被獲得到。
cn.bugstack.springframework.test.event.CustomEventListener
public class CustomEventListener implements ApplicationListener<CustomEvent> {
@Override
public void onApplicationEvent(CustomEvent event) {
System.out.println("收到:" + event.getSource() + "訊息;時間:" + new Date());
System.out.println("訊息:" + event.getId() + ":" + event.getMessage());
}
}
- 這個是一個用於監聽 CustomEvent 事件的監聽器,這裡你可以處理自己想要的操作,比如一些使用者註冊後傳送優惠券和簡訊通知等。
- 另外是關於
ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent>
、ContextClosedEventListener implements ApplicationListener<ContextClosedEvent>
監聽器,這裡就不演示了,可以參考下原始碼。
2. 配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="cn.bugstack.springframework.test.event.ContextRefreshedEventListener"/>
<bean class="cn.bugstack.springframework.test.event.CustomEventListener"/>
<bean class="cn.bugstack.springframework.test.event.ContextClosedEventListener"/>
</beans>
- 在 spring.xml 中配置了三個事件監聽器,監聽重新整理、監控自定義事件、監聽關閉事件。
3. 單元測試
public class ApiTest {
@Test
public void test_event() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
applicationContext.publishEvent(new CustomEvent(applicationContext, 1019129009086763L, "成功了!"));
applicationContext.registerShutdownHook();
}
}
- 通過使用 applicationContext 新增加的釋出事件介面方法,釋出一個自定義事件 CustomEvent,並透傳了相應的引數資訊。
測試結果
重新整理事件:cn.bugstack.springframework.test.event.ContextRefreshedEventListener$$EnhancerByCGLIB$$440a36f5
收到:cn.bugstack.springframework.context.support.ClassPathXmlApplicationContext@71c7db30訊息;時間:22:32:50
訊息:1019129009086763:成功了!
關閉事件:cn.bugstack.springframework.test.event.ContextClosedEventListener$$EnhancerByCGLIB$$f4d4b18d
Process finished with exit code 0
- 從測試結果可以看到,我們自己定義的事件和監聽,以及監聽系統的事件資訊,都可以在控制檯完整的輸出出來了。你也可以嘗試增加一些其他事件行為,並除錯程式碼學習觀察者模式。
六、總結
- 在整個手寫 Spring 框架的學習過程中,可以逐步看到很多設計模式的使用,比如:簡單工廠BeanFactory、工廠方法FactoryBean、策略模式訪問資源,現在有實現了一個觀察者模式的具體使用。所以學習 Spring 的過程中,要更加註意關於設計模式的運用,這是你能讀懂程式碼的核心也是學習的重點。
- 那麼本章節關於觀察者模式的實現過程,主要包括了事件的定義、事件的監聽和釋出事件,釋出完成後根據匹配策略,監聽器就會收到屬於自己的事件內容,並做相應的處理動作,這樣的觀察者模式其實日常我們也經常使用,不過在結合 Spring 以後,除了設計模式的學習,還可以學到如何把相應觀察者的實現和應用上下文結合。
- 所有在 Spring 學習到的技術、設計、思路都是可以和實際的業務開發結合起來的,而這些看似比較多的程式碼模組,其實也是按照各自職責一點點的擴充進去的。在自己的學習過程中,可以先動手嘗試完成這些框架功能,在一點點通過除錯的方式與 Spring 原始碼進行對照參考,最終也就慢慢掌握這些設計和編碼能力了。