写点什么

面试官系列:你对 Spring 事件发布和广播监听有了解吗?

发布于: 2021 年 02 月 15 日
面试官系列:你对Spring事件发布和广播监听有了解吗?

1、必须了解的 Spring 基础概念

1.1 BeanFacotry 与 ApplicationContext

1.1.1 BeanFactory 源码分析

我们回顾下 Spring 的小例子:


/** * <p>spring源码小例子</p> * @date: 2021/1/3 08:59 */@SuppressWarnings("deprecation")public class BeanFactoryTest {	@Test	public void testSimpleLoad(){		//BeanFactory容器的使用		BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));		MyTestBean bean = (MyTestBean) bf.getBean("myTestBean");		assertEquals("testBean", bean.getTestStr());	}}
复制代码


BeanFactory 接口的定义方法列表:

  • 接口位于类结构树的顶端, 它最主要的方法就是 getBean(StringbeanName),该方法从容器中返回特定名称的 Bean,BeanFactory 的功能通过其他的接口得到不断扩展。


BeanFactory.getBean() 源码:


	//---------------------------------------------------------------------	// Implementation of BeanFactory interface	//---------------------------------------------------------------------
@Override public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); }
@SuppressWarnings("unchecked") protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
String beanName = transformedBeanName(name); Object bean;
// Eagerly check singleton cache for manually registered singletons. //Return the (raw) singleton object registered under the given name. // 从单例池获取Bean对象 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { if (logger.isTraceEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } //获取Bean对象的实例 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); }
复制代码

代码分析:

  • 此处,我们通过构造 BeanFactory 的 实例对象 XmlBeanFactory,完成对特定 xml 文件的 Bean 信息加载,然后通过 getBean(String beanName); 方法获取特定的对象。

  • BeanFactory 在启动的时候不会去实例化 Bean,二是只有从容器中取 Bean 的时候才会去实例化;

  • BeanFactory 具备延迟实例化的优点;

  • 同时,BeanFactory 也具备不能及时发现一些存在的 Spring 的配置问题的缺点;

1.1.2 ApplicationContext 源码分析

同样我们也回顾下例子 2,如何使用 ApplicationContext 获取特定的 Bean 对象:


/** * <p>自定义标签解析</p> * @date: 2021/1/6 18:31 */public class CustomXSDTagTest {	@Test	public void testSimpleLoad() {		//读取配置文件,ApplicationContext 容器的使用		ApplicationContext bf = new ClassPathXmlApplicationContext("test.xml");		User user = (User) bf.getBean("testbean");		System.out.println(user.getEmail() + " " + user.getUserName() + "" + user.getAge());	}}
复制代码


ApplicationContext 接口的关系架构图:(ApplicationContext 本质是对 BeanFactory 进行了功能拓展)



ApplicationContext 接口的定义方法列表:


ApplicationContext 的构造器源码:

  • ApplicationContext 跟 BeanFactory 相反,它是在容器启动时,一次性创建了所有的 Bean。同时,注册 Spring 监听器的工作也发生在这里:

registerListeners();

	public ClassPathXmlApplicationContext(			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)			throws BeansException {
super(parent); setConfigLocations(configLocations); if (refresh) { //此处的refresh() 方法,会一次性将所有的bean全部装载到Spring容器 refresh(); } }
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // 解析xml配置文件 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // 注册未来bean实例化的前后置处理的PostProcessor接口实现 postProcessBeanFactory(beanFactory); //执行所有实现BeanFactoryPostProcessor接口实现,对beanFactory进行处理 invokeBeanFactoryPostProcessors(beanFactory); // 注册未来bean实例化的前后置处理的PostProcessor接口实现 registerBeanPostProcessors(beanFactory); // 注册未来bean实例化的前后置处理的PostProcessor接口实现 initMessageSource(); // 实例化spring事件发布监听机制的核心类,SimpleApplicationEventMulticaster initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // 注册事件监听器 registerListeners(); // 实例化非懒加载的bean,完成ioc容器中bean的实例化和反转依赖,并在内部实现动态代理相关的操作 finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { 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; } } }
复制代码
  • 梳理下源码的流程,见下图:


ApplicationContext.getBean()方法源码:

  • 利用工厂方法获取匹配的 Bean 对象

	//---------------------------------------------------------------------	// Implementation of BeanFactory interface	//---------------------------------------------------------------------
@Override public Object getBean(String name) throws BeansException { assertBeanFactoryActive(); return getBeanFactory().getBean(name); }
复制代码

1.1.3 BeanFacotry 与 ApplicationContext 的区别

  • BeanFacotry 是 spring 中比较原始的 Factory。

如 XMLBeanFactory 就是一种典型的 BeanFactory。原始的 BeanFactory 无法支持 spring 的许多插件,如 AOP 功能、Web 应用等。 

  • ApplicationContext 接口,它由 BeanFactory 接口派生而来,因而提供 BeanFactory 所有的功能。ApplicationContext 以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,ApplicationContext 包还提供了以下的功能: 

  • MessageSource, 提供国际化的消息访问  

  • 资源访问,如 URL 和文件  

  • 事件传播 (我们这章节的重点)

  • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的 web 层

2、Spring 事件发布与监听的应用场景

2.1 自定义事件源和事件 pojo

  • 事件源

public class TestEvent extends ApplicationEvent {	/**	 * Create a new {@code ApplicationEvent}.	 *	 * @param source the object on which the event initially occurred or with	 *               which the event is associated (never {@code null})	 */	public TestEvent(TestInfo source) {		super(source);	}}
复制代码


  • 事件 pojo

public class TestInfo {	private String info;
public String getInfo() { return info; }
public void setInfo(String info) { this.info = info; }}
复制代码

2.2 自定义事件监听器

  • ApplicationListener 需要设置泛型限定类,也就是上面提到的事件源。

@Componentpublic class TestEventListener implements ApplicationListener<TestEvent> {	@Override	public void onApplicationEvent(TestEvent event) {		TestInfo testInfo = (TestInfo) event.getSource();		System.out.println("testInfo = " + testInfo);	}}
复制代码

2.3 发布自定义事件

  • 在业务的需要地方进行事件发布

	@Autowired	private ApplicationEventPublisher applicationEventPublisher;
private void publishEvent() { TestInfo testInfo = new TestInfo(); testInfo.setInfo("zk-init"); TestEvent testEvent = new TestEvent(testInfo); applicationEventPublisher.publishEvent(testEvent); }
复制代码

2.4 代码分析

  • ApplicationContext 事件机制是观察者设计模式的实现。

  • 通过 ApplicationEvent 类和 ApplicationListener 接口,可以实现 ApplicationContext 事件处理。 如果容器中有一个 ApplicationListener Bean,每当 ApplicationContext 发布 ApplicationEvent 时,ApplicationListener Bean 将自动被触发(同步/异步的方式)。

  • 两个重要成员

ApplicationEvent:容器事件,必须由 ApplicationContext 发布;

ApplicationListener:监听器,可由容器中的任何监听器 Bean 担任。

3、源码剖析

源码剖析分为 3 个组件:

  • ApplicationEvent 事件

  • ApplicationListener 监听器,对事件进行监听

  • ApplicationEventMulticaster 事件广播器,将 publish 的事件广播给所有的监听器。

3.1 组件一:事件 ApplicationEvent 的 5 种实现

  • ContextRefreshedEvent :当 ApplicationContext 初始化或者刷新,将会发布,例如使用 ConfigurableApplicationContext 接口调用 refresh 方法,初始化意味着加载所有的 bean,同时

  • ContextStartedEvent:当 ApplicationContext 启动的时候,将会调用 start 方法,发布此事件。

  • ContextStoppedEvent:当容器停止的时候,发布事件。

  • ContextClosedEvent:当容器关闭的时候,发布事件。

  • RequestHandledEvent:http 请求完成后,发布事件。



ApplicationEvent 是所有事件的基础抽象类,自定义事件也是继承了它。

3.2 组件二:监听器 ApplicationListener

ApplicationListener:ApplicationContext 容器内部自定义事件监听器接口,继承自 java.util.EventListener,ApplicationContext 容器在启动时,会自动识别并加载 EventListener 类型 bean 的定义,一旦容器事件发布,将会通知注册到容器的监听器。


3.3 组件三:广播器 ApplicationEventMulticaster

3.3.1 发布器 ApplicationEventPublisher 和 广播器 ApplicationEventMulticaster

  • ApplicationEventPublisher:是一个封装事件发布接口,作为 ApplicationContext 父类接口。

  • ApplicationEventMulticaster:管理 ApplicationListener 对象,并且发布它们。


ApplicationContext 委托给了 AbstractApplicationEventMulticaster 来实现事件监听器(ApplicationListener)的管理。

3.3.2 发布事件 - ApplicationEventPublisher

源码

使用了 applicationEventPublisher.publishEvent() 的代码段,可以将事件发布出去。


	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {		Assert.notNull(event, "Event must not be null");
// Decorate event as an ApplicationEvent if necessary ApplicationEvent applicationEvent; if (event instanceof ApplicationEvent) { applicationEvent = (ApplicationEvent) event; } else { applicationEvent = new PayloadApplicationEvent<>(this, event); if (eventType == null) { eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType(); } }
// Multicast right now if possible - or lazily once the multicaster is initialized if (this.earlyApplicationEvents != null) { this.earlyApplicationEvents.add(applicationEvent); } else { //获取到广播器,并且将自定义事件告诉广播器。 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); }
// Publish event via parent context as well... if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); } else { this.parent.publishEvent(event); } } }
复制代码
源码分析:
  • getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

获取到广播器(SimpleApplicationEventMulticaster),并且将自定义事件告诉广播器。

3.3.3 广播器 - SimpleApplicationEventMulticaster


  • 1、SimpleApplicationEventMulticaster 广播器的类架构图:


ApplicationEventMulticaster 接口实现类是 SimpleApplicationEventMulticaster,它的 multicastEvent() 方法功能是:实现了遍历监听器列表,逐个发布事件到监听器中(观察者模式的应用场景)。



  • 上文提及的代码段:getApplicationEventMulticaster() 方法便是获取到注入的实例 SimpleApplicationEventMulticaster,它即是 ApplicationEventMulticaster 的实现类了。


  • 2、SimpleApplicationEventMulticaster 内部维护了一个监听器列表,即是一个 ConcurrentHashMap 进行管理的。


	final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
复制代码


  • 3、SimpleApplicationEventMulticaster 广播事件源码,通过 multicastEvent() 方法实现


	@Override	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));		Executor executor = getTaskExecutor();		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {			if (executor != null) {        //调用监听器的 onApplicationEvent 方法,处理事件				executor.execute(() -> invokeListener(listener, event));			}			else {        //调用监听器的 onApplicationEvent 方法,处理事件				invokeListener(listener, event);			}		}	}
复制代码


解析:最终调用 SimpleApplicationEventMulticaster 的 invokeListener() 方法进行实质事件处理。


  • SimpleApplicationEventMulticaster 的 invokeListener() 方法源码,最终调用了 doInvokeListener() 方法

最终会调用监听器的 onApplicationEvent 方法,实现监听效果。这里注意可能会抛出 ClassCastException 异常,因为事件源被业务处理时可能发生类型转换失败的情况,这样也能够捕获到这类运行时异常。

	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {		ErrorHandler errorHandler = getErrorHandler();		if (errorHandler != null) {			try {				doInvokeListener(listener, event);			}			catch (Throwable err) {				errorHandler.handleError(err);			}		}		else {			doInvokeListener(listener, event);		}	}
@SuppressWarnings({"rawtypes", "unchecked"}) private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { //若是匹配上监听器,则会调用该监听器类的 onApplicationEvent 方法 listener.onApplicationEvent(event); } catch (ClassCastException ex) { String msg = ex.getMessage(); if (msg == null || matchesClassCastMessage(msg, event.getClass()) || (event instanceof PayloadApplicationEvent && matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) { // Possibly a lambda-defined listener which we could not resolve the generic event type for // -> let's suppress the exception. Log loggerToUse = this.lazyLogger; if (loggerToUse == null) { loggerToUse = LogFactory.getLog(getClass()); this.lazyLogger = loggerToUse; } if (loggerToUse.isTraceEnabled()) { loggerToUse.trace("Non-matching event type for listener: " + listener, ex); } } else { throw ex; } } }
复制代码


  • 再走读一下源码,我们可以发现 SimpleApplicationEventMulticaster 其实是支持异步事件通知 和同步事件通知。而 SimpleApplicationEventMulticaster 作为默认的事件广播器,用的是同步通知的方式;但是 Spring 给我们提供了一个解决方案来实现我们需要的异步广播器。


那么要怎么实现异步广播器呢???

  • 1、首先需要一个自定义广播器

@Component("applicationEventMulticaster") 注解则声明了 Bean 的 name 为固定的“applicationEventMulticaster”

/** * <p> *     继承 SimpleApplicationEventMulticaster ,实现异步监听器 *     如下我们看到在以上的判断是否自定义了多播器的代码中,判断在ioc容器中是否包含如下名字的bean作为判断条件的,所以只要我们自定义一个bean命名为applicationEventMulticaster,并把异步支持的executor植入就行了 * </p> */@Component("applicationEventMulticaster")public class AsnyTestEventListener extends SimpleApplicationEventMulticaster {	public AsnyTestEventListener () {		setTaskExecutor(Executors.newSingleThreadExecutor());	}}
复制代码
  • 2、源码分析(自定义广播器是如何被注册到 Spring 容器的)

2.1、我们走读一下 AbstractApplicationContext 的源码,注意到一个静态字符串变量的值为“applicationEventMulticaster”;

2.2、同时定位到 initApplicationEventMulticaster() 方法的作用就是 Initialize the ApplicationEventMulticaster.(初始化事件广播器),如果可以获取到则使用这个“applicationEventMulticaster”Bean,则可以进行注册了(其实就是获取对象引用然后赋值)。

	public abstract class AbstractApplicationContext extends DefaultResourceLoader		implements ConfigurableApplicationContext {    /**     * Name of the ApplicationEventMulticaster bean in the factory.     * If none is supplied, a default SimpleApplicationEventMulticaster is used.     * @see org.springframework.context.event.ApplicationEventMulticaster     * @see org.springframework.context.event.SimpleApplicationEventMulticaster     */    public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
/** * Initialize the ApplicationEventMulticaster. * Uses SimpleApplicationEventMulticaster if none defined in the context. * @see org.springframework.context.event.SimpleApplicationEventMulticaster */ protected void initApplicationEventMulticaster() { ConfigurableListableBeanFactory beanFactory = getBeanFactory(); //Bean工厂是否可以获取到 applicationEventMulticaster 的Bean if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); if (logger.isTraceEnabled()) { logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); } } else { //获取不到自定义的广播器,那么就使用默认的 SimpleApplicationEventMulticaster this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); if (logger.isTraceEnabled()) { logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " + "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]"); } } } }
复制代码


当在我们自定义的多播器中设置了 executor 时,SimpleApplicationEventMulticaster 广播器的 exeutor 就不为空了 ,就会走到第一个异步多播的路径。

4、延伸阅读

《源码系列》

JDK之Object 类

JDK之BigDecimal 类

JDK之String 类

JDK之Lambda表达式

JDK之内部类


《经典书籍》

Java并发编程实战:第1章 多线程安全性与风险

Java并发编程实战:第2章 影响线程安全性的原子性和加锁机制

Java并发编程实战:第3章 助于线程安全的三剑客:final & volatile & 线程封闭


《服务端技术栈》

《Docker 核心设计理念

《Kafka史上最强原理总结》

《HTTP的前世今生》


《算法系列》

读懂排序算法(一):冒泡&直接插入&选择比较

《读懂排序算法(二):希尔排序算法》

《读懂排序算法(三):堆排序算法》

《读懂排序算法(四):归并算法》

《读懂排序算法(五):快速排序算法》

《读懂排序算法(六):二分查找算法》


《设计模式》

设计模式之六大设计原则

设计模式之创建型(1):单例模式

设计模式之创建型(2):工厂方法模式

设计模式之创建型(3):原型模式

设计模式之创建型(4):建造者模式

设计模式之创建型(5):抽象工厂模式



发布于: 2021 年 02 月 15 日阅读数: 27
用户头像

Diligence is the mother of success. 2018.03.28 加入

公众号:后台技术汇 笔者主要从事Java后台开发,喜欢技术交流与分享,保持饥渴,一起进步!

评论

发布
暂无评论
面试官系列:你对Spring事件发布和广播监听有了解吗?