Spring 拓展原理之事件监听源码级教程

322 阅读7分钟

Spring 拓展原理之事件监听源码级教程

开篇

哈喽,大家好,我是尘心,一个每天都在电脑前酣战淋漓的男人!说起Spring,想必大家都不陌生,但是想要精通Spring各类设计原理,估计还有很长的一段路要走。今天我们来分析一下,Spring拓展原理中的事件监听。事件监听在实际的开发工作当中使用还是比较广泛的,如果你还不知道,那么一起来学习一下吧

注:作者代码版本均使用【4.3.12.RELEASE】

maven 依赖:

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>

系统事件派发【源码剖析】

初探究竟

我们编写一个自己的监听器,用于监听容器中的事件。实际代码如下:

/**
 * 使用 Component 注解加入到容器中
 */
@Component
public class MyActionListener implements ApplicationListener<ApplicationEvent> {

    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("收到事件:" + event);
    }
}

运行代码,查看控制台:

image-20210611103135411.png 从控制台看出,只要容器一启动就会调用onApplicationEvent 方法,触发ApplicationEvent 事件。

奇怪,我们没有发布任何事件,为什么会有事件触发呢?原因是IOC容器一启动就会发布一些基础事件,告知用户,方便用户处理。我们点进去查看一下源代码:

深入探究

1) 这是我们编写的启动类,我们采用的是AnnotationConfigApplicationContext注解启动IOC容器

public class Application {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        // 打印所有容器中bean的名称
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }
}
  1. 跟踪AnnotationConfigApplicationContext 进去,发现有以下几个方法:
	public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
                // 调用构造器
		this();
                // 根据注解注册一些bean的定义信息
		register(annotatedClasses);
                // 刷新容器
		refresh();
	}

前两步骤都是一些注册定义等等,没有实际的创建工作,所以我们猜测,事件发布是在刷新容器的时候调用

  1. 跟踪 refresh()方法,进去发现它是IOC容器的核心处理
	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				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;
			}
		}
	}

我们重点看到了 initApplicationEventMulticaster(); 初始化事件派发器 和 registerListeners(); 注册监听器方法

事件派发器和监听器都准备好了,那么派发事件肯定是在后面。

  1. 跟踪 finishRefresh(); 方法进去,果不其然,在这里调用了publishEvent发布了 ContextRefreshedEvent 事件. 而我们控制台打印的就是这个事件!
	/**
	 * Finish the refresh of this context, invoking the LifecycleProcessor's
	 * onRefresh() method and publishing the
	 * {@link org.springframework.context.event.ContextRefreshedEvent}.
	 */
	protected void finishRefresh() {
		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// Publish the final event. 在这里发布了一个 ContextRefreshedEvent事件
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}
  1. 系统事件除了我们刚才监听的那几个,还有如下几个:

image-20210609123036888.png

我们刚才使用的是父类事件,系统默认派发的是子类ContextRefreshedEvent 事件。 Spring版本请参考开篇中的版本定义

其他事件都是系统已经定义好的,直接使用就可以啦。下面我们重点说一下,自定义事件的派发和监听!

自定义事件派发【代码实战】

  1. 编写自己的事件

想要我们自己想要发布事件,其实很简单,编写自定义事件类继承ApplicationEvent,拿到IOC容器,直接调用派发方法即可

// 这里我们编写一个自定义事件
public class MyApplicationEvent extends ApplicationEvent {

    /**
     * Create a new ApplicationEvent.
     *
     * @param source the component that published the event (never {@code null})
     */
    public MyApplicationEvent(Object source) {
        super(source);
    }
}
  1. 主动派发自定义事件

使用context 的 publishEvent 方法派发事件

public class Application {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println("发布事件:MyApplicationEvent");
        context.publishEvent(new MyApplicationEvent("事件MyApplicationEvent"));

        System.out.println("发布事件:MyApplicationEvent2");
        context.publishEvent(new MyApplicationEvent("事件MyApplicationEvent2"));
    }
}
  1. 监听自定义事件
@Component
public class MyActionListener implements ApplicationListener<MyApplicationEvent>{

    public void onApplicationEvent(MyApplicationEvent event) {
        /**
         * 这里我们监听自己的事件,
         * 当有此事件派发, 此方法就会触发
         */
        Object source = event.getSource();
        System.out.println("监听到事件:"+source);
    }
}

4) 运行容器,查看结果

image-20210611112206540.png

可以看到,我们自己发布的事件,自己监听处理。发布几次就处理几次,是不是很酸爽呢?

系统如何派发自定义事件?【源码剖析】

上一章节我们演示了如何编写自定义事件,派发以及监听自定义事件。那么我们编写的事件,Spring是如何感知和帮我们派发事件的呢?在 <系统事件派发> 章节中,在容器初始化时,我们看到了这两个方法:如果忘了,可以返回到上面 “系统事件派发” 章节再看一次!

 initApplicationEventMulticaster(); // 初始化事件派发器
 registerListeners();// 注册监听器

初始化派发器

初始化派发器,先拿到beanFactory, 然后从本地容器中查找有没有创建好的派发器,如果没有则注册创建一个单例的 SimpleApplicationEventMulticaster 派发器放到容器中

	protected void initApplicationEventMulticaster() {
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
		else {
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
						APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
						"': using default [" + this.applicationEventMulticaster + "]");
			}
		}
	}

注册监听器

  1. 容器会拿到所有监听器,然后进行遍历添加到 派发器 的监听器List集合中

  2. 根据 ApplicationListener 类型拿到所有监听器的名称,存到 派发器的Set集合中

	protected void registerListeners() {
		// Register statically specified listeners first.
		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!
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		for (String lisName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(lisName);
		}
	}

派发事件

现在派发器,监听器都已经准备就绪,当我们调用发布事件方法时则触发派发方法

	@Override
	public void publishEvent(ApplicationEvent event) {
		Assert.notNull(event, "Event must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Publishing event in " + getDisplayName() + ": " + event);
		}
		getApplicationEventMulticaster().multicastEvent(event);
		if (this.parent != null) {
			this.parent.publishEvent(event);
		}
	}
  1. getApplicationEventMulticaster() 拿到事件派发器

  2. multicastEvent() 发布事件, 观察源码,拿到事件对应的所有监听器,然后启动线程执行invokeListener

	@Override
	public void multicastEvent(final ApplicationEvent event) {
		for (final ApplicationListener<?> listener : getApplicationListeners(event)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(new Runnable() {
					@Override
					public void run() {
						invokeListener(listener, event);
					}
				});
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
  1. invokeListener() 执行监听器,在这里我们清楚的看到,监听器执行了 onApplicationEvent 方法,就是我们自定义监听器时,重写的方法。
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
   ErrorHandler errorHandler = getErrorHandler();
   if (errorHandler != null) {
      try {
         listener.onApplicationEvent(event);
      }
      catch (Throwable err) {
         errorHandler.handleError(err);
      }
   }
   else {
      listener.onApplicationEvent(event);
   }
}

至此源码分析就告一段落,想必你已经掌握了Spring事件监听的核心啦。

高阶用法之@EventListener

在实际开发中,Spring为了方便使用,只需要在想要监听事件的方法上,使用@EventListener注解就能监听事件。

  1. 在想要监听的方法上标注注解
@Service
public class UserService {


    @EventListener(classes = MyApplicationEvent.class)
    public void listener(MyApplicationEvent event) {
        Object source = event.getSource();
        System.out.println("注解收到事件:"+source);
    }
}

2)监听方法的参数,就是你要传递的事件

3)启动容器,查看效果 。 非常酸爽!

image-20210611124512394.png

  1. 再说原理

原理其实很简单,和我们之前分析的差不多,只是注入对象采用注解注入了。首先扫描对应的注解,然后事件派发时,调用该方法,然后传入事件。

注: 需要特别注意的是, 低版本的Spring是没有提供这个注解的。亲测Sping4.1.12版本是没有的。

结尾

SpringBoot事件监听机制 和 Spring几乎一致,所以各位小伙伴不必担心。掌握源码,则万变不离其宗。我是 尘心,如果这篇文章对你有帮助,可以点个赞鼓励一下。公众号(GitHub严选),技术经验与好用黑科技工具一起分享,进去先点福利清单,海量福利等着你。