Spring原始碼之七registerListeners()及釋出訂閱模式

newknight發表於2022-03-07

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完成事件釋出。

image-20220307143024731

事件:

@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中起作用的。

image-20220307144531284

第一個迴圈是Spring預設的監聽器預設情況下是空。

我們自定義的事件監聽器在第二個迴圈裡面被載入到了ApplicationEventMulticaster中,已經很明顯了,ApplicationEventMulticaster就是我們的事件管理者。

我們在把他們之間的關係詳細串一下。

image-20220307144952688

注:廣播器和上面的管理者是一個意思

到這個階段,事件管理者和監聽器都在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上面的斷點有什麼區別。

image-20220307151529352

在Spirng中監聽器預設是空,當時在Springboot中有15個之多,不愧是加強版的Spring。具體這些監聽器是幹嘛的我們就不深究了,在Springboot中會對其逐步拆解的。

好啦,今天對registerListeners()的方法的剖析也就結束啦。

相關文章