Spring原始碼之七registerListeners()及釋出訂閱模式
大家好,我是程式設計師田同學。
今天帶大家解讀refresh()方法中的registerListeners()方法,也就是我們經常說的Spring的釋出-訂閱模式。文章首先舉一個釋出-訂閱模式的樣例,然後講解了釋出-訂閱四個模式的原理,及對釋出-訂閱模式所依賴的觀察者模式進行了舉例,最後引出該模式在Springboot中的大量應用。
照例放一份refresh()方法的原始碼,registerListeners()方法位於該方法的第七個位置。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//1、重新整理前的準備
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//2、將會初始化 BeanFactory、載入 Bean、註冊 Bean
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//3、設定 BeanFactory 的類載入器,新增幾個 BeanPostProcessor,手動註冊幾個特殊的 bean
prepareBeanFactory(beanFactory);
try {
//4、模板方法
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//執行BeanFactory後置處理器
invokeBeanFactoryPostProcessors(beanFactory);
// 5、Register bean processors that intercept bean creation.
//註冊bean後置處理器
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
//國際化
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//6、模板方法--springboot實現了這個方法
onRefresh();
// Check for listener beans and register them.
//7、註冊監聽器
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//8、完成bean工廠的初始化**方法重要**********************************************
finishBeanFactoryInitialization(beanFactory);
//9、 Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
先大致看一下registerListeners()方法的原始碼。
protected void registerListeners() {
// Register statically specified listeners first.
// 首先註冊靜態的指定的監聽器,註冊的是特殊的事件監聽器,而不是配置中的bean
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
// 這裡不會初始化FactoryBean,我們需要保留所有的普通bean
// 不會例項化這些bean,讓後置處理器可以感知到它們
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
// Publish early application events now that we finally have a multicaster...
// 現在有了事件廣播組,釋出之前的應用事件
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
這個方法要做的很簡單,就是兩個迴圈遍歷,把Spring通過硬編碼定義的監聽器註冊到容器中,然後把我們自定義的監聽器註冊到容器中,通過這些直接敘述有一些蒼白無力,我們寫一個簡單的釋出-訂閱的例子方便理解。
在釋出訂閱模式用需要四個角色:
ApplicationEvent:事件,每個實現類表示一類事件,可攜帶資料。抽象類。
ApplicationListener:事件監聽器,用於接收事件處理時間。介面。
ApplicationEventMulticaster:事件管理者,可以註冊(新增)/移除/釋出事件。用於事件監聽器的註冊和事件的廣播。介面。
ApplicationEventPublisher:事件釋出者,委託事件管理者ApplicationEventMulticaster完成事件釋出。
事件:
@Component
public class MyEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public MyEvent(Object source) {
super(source);
}
}
事件監聽器:
@Component
public class MyEventListener implements ApplicationListener<MyEvent> {
@EventListener //@EventListener註解實現事件監聽
@Override
public void onApplicationEvent(MyEvent event) {
Object msg = event.getSource();
System.out.println("自定義事件監聽器(MyEventListener1)收到釋出的訊息: " + msg);
}
}
事件釋出者:
public static void main(String[] args) {
System.out.println(1);
ApplicationContext ac =new AnnotationConfigApplicationContext(MyEventListener.class);
MyEvent myEvent=new MyEvent(new Object());
ac.publishEvent(myEvent);
}
那麼事件的管理器跑哪去了呢?
我們在 registerListeners()方法上面打一個斷點,看看我們自定義的事件是如何在Spring中起作用的。
第一個迴圈是Spring預設的監聽器預設情況下是空。
我們自定義的事件監聽器在第二個迴圈裡面被載入到了ApplicationEventMulticaster中,已經很明顯了,ApplicationEventMulticaster就是我們的事件管理者。
我們在把他們之間的關係詳細串一下。
注:廣播器和上面的管理者是一個意思
到這個階段,事件管理者和監聽器都在Spring容器裡初始化和註冊了,之後就可以實現監聽者模式了,對事件的釋出進行監聽然後處理。
在就是ac.publishEvent(myEvent);方法釋出事件後進行的業務處理。
ApplicationContext ac =new AnnotationConfigApplicationContext(MyEventListener.class);
MyEvent myEvent=new MyEvent(new Object());
ac.publishEvent(myEvent);
事件監聽機制實際就是主題-訂閱模式(觀察者模式)的實現,能夠降低程式碼耦合。
本文順便把觀察者模式簡要的敘述一下,方便讀者可以更好的理解。
觀察者(Observer)模式的定義:指多個物件間存在一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。
public class RMBrateTest {
public static void main(String[] args) {
Rate rate = new RMBrate();
Company watcher1 = new ImportCompany();
Company watcher2 = new ExportCompany();
rate.add(watcher1);
rate.add(watcher2);
rate.change(10);
rate.change(-9);
}
}
//抽象目標:匯率
abstract class Rate {
protected List<Company> companys = new ArrayList<Company>();
//增加觀察者方法
public void add(Company company) {
companys.add(company);
}
//刪除觀察者方法
public void remove(Company company) {
companys.remove(company);
}
public abstract void change(int number);
}
//具體目標:人民幣匯率
class RMBrate extends Rate {
public void change(int number) {
for (Company obs : companys) {
((Company) obs).response(number);
}
}
}
//抽象觀察者:公司
interface Company {
void response(int number);
}
//具體觀察者1:進口公司
class ImportCompany implements Company {
public void response(int number) {
if (number > 0) {
System.out.println("人民幣匯率升值" + number + "個基點,降低了進口產品成本,提升了進口公司利潤率。");
} else if (number < 0) {
System.out.println("人民幣匯率貶值" + (-number) + "個基點,提升了進口產品成本,降低了進口公司利潤率。");
}
}
}
//具體觀察者2:出口公司
class ExportCompany implements Company {
public void response(int number) {
if (number > 0) {
System.out.println("人民幣匯率升值" + number + "個基點,降低了出口產品收入,降低了出口公司的銷售利潤率。");
} else if (number < 0) {
System.out.println("人民幣匯率貶值" + (-number) + "個基點,提升了出口產品收入,提升了出口公司的銷售利潤率。");
}
}
}
匯率變化就是一個事件,當該事件發生時,它的觀察者出口公司和進口公司就要做相應的變化。
書歸正傳重新回到registerListeners()方法中,這時所有的監聽器都註冊到了容器中,只等publishEvent()釋出事件以後,就會執行我們監聽器中的業務邏輯。
據說在Springboot中應用了大量的釋出訂閱模式,抱著求知若渴的態度我們去Springboot中摟一眼,在registerListeners()上面打一個斷點,看看和Spring上面的斷點有什麼區別。
在Spirng中監聽器預設是空,當時在Springboot中有15個之多,不愧是加強版的Spring。具體這些監聽器是幹嘛的我們就不深究了,在Springboot中會對其逐步拆解的。
好啦,今天對registerListeners()的方法的剖析也就結束啦。