SpringFramework事件与监听机制(发布器)

SpringBoot版本:2.0.2.RELEASE
SpringFramework版本:5.0.6.RELEASE

谁是SpringFramework的事件发布器

在这里插入图片描述从UML图看,ApplicationEventPublisher只声明了发布事件的方法,到了ConfigurableApplicationContext才补充了与监听器关联的方法。这些方法最终由AbstractApplicationContext实现。而它的子类一般也没有重载这些方法。因此,从类定义的角度来看,AbstractApplicationContext承担了发布事件的责任。
那么在一个SpringBoot工程里,它是如何成为事件发布者呢?在前文多处已经暗示,有一个发布器移交的这么一个过程,详见EventPublishingRunListener的各发布事件方法。
在SpringApplication#run方法的整体过程控制下,在SpringBoot发布ApplicationPreparedEvent事件的时候,有个将SpringBoot自己的Listener添加到ConfigurableApplicationContext的动作。然后AbstractApplicationContext#refresh方法会被调用,在该方法里完成了加载SpringFramework的监听器和初始化了SpringFramework层面的事件发布器SimpleApplicationEventMulticaster,最后调用finishRefresh方法发布ContextRefreshedEvent事件。下面列出AbstractApplicationContext#refresh方法内重点函数:

@Override
public void refresh() throws BeansException, IllegalStateException {
    
    
	....
	// Prepare the bean factory for use in this context.
	prepareBeanFactory(beanFactory);
	....
	// Initialize event multicaster for this context.
	initApplicationEventMulticaster();
	....
	// Check for listener beans and register them.
	registerListeners();
	....
	// Last step: publish corresponding event.
	finishRefresh();
}

这些函数不难,读者可自行翻阅。但有一点要注意的整个SpringBoot启动过程中,事件发布的顺序,以及在发布各个事件的时候系统为此做了什么准备工作。控制着这些节奏的其实是SpringApplication,在它的run方法里已经编好了整个大纲。

双子座的消息发布者

在《SpringBoot事件与监听机制》已经知道SpringBoot的事件发布器是EventPublishingRunListener,而它最终是委托SimpleApplicationEventMulticaster的来完成事件的发布。
在EventPublishingRunListener#contextLoaded方法看到为发点布器交棒的准备,在started和running方法,已经看到发布事件的工作交给了ConfigurableApplicationContext去完成。我们回头看contextLoaded方法,只是将SpringBoot的监听器添加到ConfigurableApplicationContext,但并没有将自己的SimpleApplicationEventMulticaster交过去,那么ConfigurableApplicationContext是用什么来发布信息呢?
在AbstractApplicationContext里我们看到ApplicationEventMulticaster接口类型属性的声明:

/** Helper class used in event publishing */
	@Nullable
	private ApplicationEventMulticaster applicationEventMulticaster;

在AbstractApplicationContext#refresh方法里会调用initApplicationEventMulticaster函数,里面就对该变量进行赋值:

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 + "]");
			}
		}
	}

就是说,AbstractApplicationContext也有一个SimpleApplicationEventMulticaster作为自己的属性,名字叫applicationEventMulticaster。
我们再来观察AbstractApplicationContext是如何发布事件的,我们从publishEvent(ApplicationEvent event)方法开始跟踪,然后会来到publishEvent(Object event, @Nullable ResolvableType eventType)方法:

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    
    
		....
		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方法如下:

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
    
    
		if (this.applicationEventMulticaster == null) {
    
    
			throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
					"call 'refresh' before multicasting events via the context: " + this);
		}
		return this.applicationEventMulticaster;
	}

到这里就明白了,SpringFramwork最终也是用SimpleApplicationEventMulticaster来发布事件,但此SimpleApplicationEventMulticaster并不是SpringBoot的SimpleApplicationEventMulticaster。那么这样的话会产生什么效果呢?前文已多次讲到在AbstractApplicationContext#refresh方法执行前,在EventPublishingRunListener#contextLoaded方法里已经将SpringBoot监听器添加给ConfigurableApplicationContext(即AbstractApplicationContext),所以后续SpringBoot委托AbstractApplicationContext发布的任何事件,SpringBoot的监听器都有可能会收到(“有可能”是因为还有事件类型以及事件源类型匹配的问题在里面)。
从EventPublishingRunListener各发布事件的方法看来,SpringBoot层面的SimpleApplicationEventMulticaster只是起到临时发布事件的作用,在ConfigurableApplicationContext未完成refresh前,SpringBoot启动经历的各状态先由SpringBoot层面的SimpleApplicationEventMulticaster发布事件。当ConfigurableApplicationContext完成refresh后,就交由它去完成发布事件的工作。那么为什么SpringBoot不把自己的EventPublishingRunListener传给ConfigurableApplicationContext呢?其实在SpringBoot1.x版本里确实是这样做的,到了2.x版本就调整成现在这样。为什么有这个调整,我猜SpringBoot毕竟是基于SpringFramework发展而来,但它毕竟不是SpringFramework的一部分,二者存在一定的独立性,所以作者有这么一个调整。以后随着实践的反馈,往后会怎么调整计划,我们再一起见证吧。

发布的事件类型

Spring Framework除了支持实现了ApplicationEvent接口事件的发布外,还支持泛型事件的发布,具体代码位置在AbstractApplicationContext#publishEvent:

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    
    
		....
		// 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);
		}

		....
	}

当事件不属于ApplicationEvent类型的时候,就以PayloadApplicationEvent进行封装,后续会有专门的文章介绍。在此先有个印象,Spring Framework是支持泛型事件的发布。

自定义事件监听者

有了《SpringBoot事件与监听机制》和本文的探讨,我们大概知道知道SpringBoot的启停过程中如何加载和交接监听器。我们可以粗略画出以下图:

Created with Raphaël 2.2.0 开始 从/Meta-INF/spring.factories加载SpringBoot监听器 从/Meta-INF/spring.factories加载SpringApplicationRunListener监听器 发布ApplicationStartingEvent事件 发布ApplicationEnvironmentPreparedEvent事件 添加Listener给Context和发布ApplicationPreparedEvent事件 Context加载监听器并发布ContextRefreshedEvent事件 发布ApplicationStartedEvent事件 发布ApplicationReadyEvent事件 发布ContextClosedEvent事件 结束

下面的实验希望达到这样的效果:
1.有个监听器能监听到所有的事件
2.有个监听器只能监听SrpingBoot的一种事件(及子事件)和SpringFramework的两种事件(及子事件)。
3.监听器能够通过配置的形式加载到系统里

经过前文的分析,如果我们想添加自定义的监听器的话,最合适的方式莫过于在/Meta-INF/spring.factories添加配置了。
为了实现本实验功能,我们自定义两个配置类:

  • CustomizedListener1
    它要监听所有的事件,比较方便的方法是实现ApplicationListener接口并指定泛型为ApplicationEvent
public class CustomerizedListener1 implements ApplicationListener<ApplicationEvent> {
    
    
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
    
    
        System.out.println(Thread.currentThread().getName() + ":CustomerizedListener1:" + event.getClass().getName());
    }
}
  • CustomerizedListener2
    它只监听部分的事件,可实现GenericApplicationListener接口或者SmartApplicationListener接口
public class CustomerizedListener2 implements GenericApplicationListener {
    
    

    @Override
    public boolean supportsEventType(ResolvableType eventType) {
    
    
        Class<?> type = eventType.getRawClass();
        if (type == null) {
    
    
            return false;
        }
        return ApplicationStartingEvent.class.isAssignableFrom(type)
                || ContextRefreshedEvent.class.isAssignableFrom(type)
                || ContextClosedEvent.class.isAssignableFrom(type);
    }

    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
    
    
        return true;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
    
    
        System.out.println( Thread.currentThread().getName() + ":CustomizedListener2:" + event.getClass().getName()  );
    }

    @Override
    public int getOrder() {
    
    
        return 0;
    }
}
  • spring.factory配置如下:
org.springframework.context.ApplicationListener=test.listener.CustomerizedListener1,test.listener.CustomerizedListener2

运行结果如下:

D:\Software\Java\jdk1.8.0_111\bin\java.exe "-javaagent:D:\Software\JetBrains\IntelliJ IDEA Community Edition 2019.3.4\lib\idea_rt.jar=55667:D:\Software\JetBrains\IntelliJ IDEA Community Edition 2019.3.4\bin" -Dfile.encoding=UTF-8 -classpath D:\Software\Java\jdk1.8.0_111\jre\lib\charsets.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\deploy.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\access-bridge-64.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\cldrdata.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\dnsns.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\jaccess.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\jfxrt.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\localedata.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\nashorn.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\sunec.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\sunjce_provider.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\sunmscapi.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\sunpkcs11.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\ext\zipfs.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\javaws.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\jce.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\jfr.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\jfxswt.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\jsse.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\management-agent.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\plugin.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\resources.jar;D:\Software\Java\jdk1.8.0_111\jre\lib\rt.jar;D:\Temp\mywebapp\target\classes;D:\m2_repository\org\springframework\boot\spring-boot-loader\2.0.2.RELEASE\spring-boot-loader-2.0.2.RELEASE.jar;D:\m2_repository\org\springframework\boot\spring-boot-starter\2.0.2.RELEASE\spring-boot-starter-2.0.2.RELEASE.jar;D:\m2_repository\org\springframework\boot\spring-boot\2.0.2.RELEASE\spring-boot-2.0.2.RELEASE.jar;D:\m2_repository\org\springframework\spring-context\5.0.6.RELEASE\spring-context-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\spring-aop\5.0.6.RELEASE\spring-aop-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\spring-beans\5.0.6.RELEASE\spring-beans-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\spring-expression\5.0.6.RELEASE\spring-expression-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\boot\spring-boot-autoconfigure\2.0.2.RELEASE\spring-boot-autoconfigure-2.0.2.RELEASE.jar;D:\m2_repository\org\springframework\boot\spring-boot-starter-logging\2.0.2.RELEASE\spring-boot-starter-logging-2.0.2.RELEASE.jar;D:\m2_repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\m2_repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\m2_repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;D:\m2_repository\org\apache\logging\log4j\log4j-to-slf4j\2.10.0\log4j-to-slf4j-2.10.0.jar;D:\m2_repository\org\apache\logging\log4j\log4j-api\2.10.0\log4j-api-2.10.0.jar;D:\m2_repository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;D:\m2_repository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\m2_repository\org\springframework\spring-core\5.0.6.RELEASE\spring-core-5.0.6.RELEASE.jar;D:\m2_repository\org\springframework\spring-jcl\5.0.6.RELEASE\spring-jcl-5.0.6.RELEASE.jar;D:\m2_repository\org\yaml\snakeyaml\1.19\snakeyaml-1.19.jar;D:\m2_repository\com\fasterxml\jackson\core\jackson-databind\2.9.5\jackson-databind-2.9.5.jar;D:\m2_repository\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;D:\m2_repository\com\fasterxml\jackson\core\jackson-core\2.9.5\jackson-core-2.9.5.jar test.MyClass
main:CustomizedListener2:org.springframework.boot.context.event.ApplicationStartingEvent
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationStartingEvent
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.2.RELEASE)

2020-08-16 15:47:42.608  INFO 9908 --- [           main] test.MyClass                             : Starting MyClass on yangyongbin-PC with PID 9908 (D:\Temp\mywebapp\target\classes started by yangyongbin in D:\Temp\mywebapp)
2020-08-16 15:47:42.620  INFO 9908 --- [           main] test.MyClass                             : No active profile set, falling back to default profiles: default
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationPreparedEvent
2020-08-16 15:47:42.779  INFO 9908 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6591f517: startup date [Sun Aug 16 15:47:42 CST 2020]; root of context hierarchy
2020-08-16 15:47:46.840  INFO 9908 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
main:CustomizedListener2:org.springframework.context.event.ContextRefreshedEvent
main:CustomerizedListener1:org.springframework.context.event.ContextRefreshedEvent
2020-08-16 15:47:46.878  INFO 9908 --- [           main] test.MyClass                             : Started MyClass in 5.234 seconds (JVM running for 6.8)
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationStartedEvent
main:CustomerizedListener1:org.springframework.boot.context.event.ApplicationReadyEvent
2020-08-16 15:47:46.895  INFO 9908 --- [       Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6591f517: startup date [Sun Aug 16 15:47:42 CST 2020]; root of context hierarchy
Thread-2:CustomizedListener2:org.springframework.context.event.ContextClosedEvent
Thread-2:CustomerizedListener1:org.springframework.context.event.ContextClosedEvent
2020-08-16 15:47:46.899  INFO 9908 --- [       Thread-2] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

Process finished with exit code 0

从运行结果可知,CustomerizedListener1监听了全部的事件,CustomerizedListener2也监听到了指定的事件。并且它们都是在另一个线程里监听到ContextClosedEvent事件。这些都符合前文的描述。
在《SpringFramework事件与监听机制(事件)》文章里介绍了ContextClosedEvent事件是怎么样在另一个线程发布的。

猜你喜欢

转载自blog.csdn.net/yyb_gz/article/details/107891510