SpringFramework事件与监听机制(事件)

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

SpringFramework事件与监听机制

SpringFramework的事件

随着SpringBoot工程的启动,程序会历经以下代码段:

SpringApplication.run(XXXX.class,args);
....
return new SpringApplication(primarySources).run(args);
....

我们先把目光放在SpringApplication#run方法。该方法有一些命令要重点关注:

....
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
....
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
....
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
refreshContext(context);
....
listeners.started(context);
....
listeners.running(context);
....

在《SpringBoot事件与监听机制》已经说明listeners作为SpringBoot的事件发布者,它的行为就是发布SpringBoot事件。而SpringFramework事件的发布在refreshContext(context)函数里,我们继续进入窥探之。一路跟踪,来到下图位置:

/**
	 * Refresh the underlying {@link ApplicationContext}.
	 * @param applicationContext the application context to refresh
	 */
	protected void refresh(ApplicationContext applicationContext) {
    
    
		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
		((AbstractApplicationContext) applicationContext).refresh();
	}

接下来,程序将离开SpringApplication的内部方法调用,进入AbstractApplicationContext#refresh方法。从类的定义来看,AbstractApplicationContext实现了ConfigurableApplicationContext接口,所以它要实现refresh方法。
我们继续跟踪下去,来到AbstractApplicationContext#finishRefresh方法,如下图:

/**
	 * Finish the refresh of this context, invoking the LifecycleProcessor's
	 * onRefresh() method and publishing the
	 * {@link org.springframework.context.event.ContextRefreshedEvent}.
	 */
	protected void finishRefresh() {
    
    
		// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

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

		// Publish the final event.
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	}

略懂英文的话,就会被publishEvent(new ContextRefreshedEvent(this));命令吸引住。该命令就发布了ContextRefreshedEvent事件。而在整个SpringBoot的启动过程中,SpringFramework就仅发布了这个事件。那SpringFramework的事件就仅仅只有ContextRefreshedEvent一个?肯定不是的,下文继续分析。

SpringFramework与SpringBoot的事件关系

类定义层面的关系

SpringFramework事件的UML图如下:
在这里插入图片描述
在SpringBoot的启停过程中,主要涉及四个事件。PayloadApplictionEvent是让Spring Framework具有发布i泛型事件的能力,后面会有专门的文章介绍泛型事件的机制。假如我们将SpringBoot的事件也纳入一起观察,会得到以下UML图:
在这里插入图片描述

从上图可知,SpringBoot族的事件与SpringFramework族事件同源,但ApplicationEvent来自SpringFramework。

发布事件的时机

我们先把目光放回SpringApplication#run方法。

....
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
....
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
....
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
refreshContext(context);
....
listeners.started(context);
....
listeners.running(context);
....

listeners.starting();发布ApplicationStartingEvent事件;
prepareEnvironment(listeners,applicationArguments);发布ApplicationEnvironmentPreparedEvent事件;
prepareContext(context, environment, listeners, applicationArguments,printedBanner);发布ApplicationPreparedEvent事件;
refreshContext(context);发布ContextRefreshedEvent事件;
listeners.started(context);发布ApplicationStartedEvent事件;
listeners.running(context);发布ApplicationReadyEvent事件;

从发布事件的行为来说,SpringBoot都是通过SpringApplicationRunListeners来发布的,即上面的listeners对象,而SpringFramework则通过ConfigurableApplicationContext来发布,即上面的context对象。从prepareContext命令开始,SpringBoot都围绕着context对象的大部分生命周期而发布消息。在prepareContext方法里,会看到这两条命令:

private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
    
    
	....
	listeners.contextPrepared(context);
	....
	listeners.contextLoaded(context);
}

在SpringBoot程序结束的时候,也有照顾到ConfigurableApplicationContext,但处理方式稍有特别。同样是在SpringApplication#run方法里:

/public ConfigurableApplicationContext run(String... args) {
    
    
....
	prepareContext(context, environment, listeners, applicationArguments, printedBanner);
	refreshContext(context);
	afterRefresh(context, applicationArguments);
....

进入refreshContext方法:

private void refreshContext(ConfigurableApplicationContext context) {
    
    
		refresh(context);
		if (this.registerShutdownHook) {
    
    
			try {
    
    
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
    
    
				// Not allowed in some environments.
			}
		}
	}

refresh(context)命令前文有提到,最终ConfigurableApplicationContext会发布ContextRefreshedEvent事件。而context.registerShutdownHook()方法在AbstractApplicationContext定义:

/**
	 * Register a shutdown hook with the JVM runtime, closing this context
	 * on JVM shutdown unless it has already been closed at that time.
	 * <p>Delegates to {@code doClose()} for the actual closing procedure.
	 * @see Runtime#addShutdownHook
	 * @see #close()
	 * @see #doClose()
	 */
	@Override
	public void registerShutdownHook() {
    
    
		if (this.shutdownHook == null) {
    
    
			// No shutdown hook registered yet.
			this.shutdownHook = new Thread() {
    
    
				@Override
				public void run() {
    
    
					synchronized (startupShutdownMonitor) {
    
    
						doClose();
					}
				}
			};
			Runtime.getRuntime().addShutdownHook(this.shutdownHook);
		}
	}

意思就是创建一个子线程对象,挂载到JDK的运行时对象。
ConfigurableApplicationContext#registerShutdownHook接口方法的说明是这样的:

/**
	 * Register a shutdown hook with the JVM runtime, closing this context
	 * on JVM shutdown unless it has already been closed at that time.
	 * <p>This method can be called multiple times. Only one shutdown hook
	 * (at max) will be registered for each context instance.
	 * @see java.lang.Runtime#addShutdownHook
	 * @see #close()
	 */

Runtime#addShutdownHook的描述比较长,在此就不贴出来了,读者有兴趣可自行翻阅。其中意思就是注册的线程会在操作系统结束时、^C、应用正常结束等通过JDK调用。里面也提到什么时候不会产生效果。我们继续看doClose方法:

//**
	 * Actually performs context closing: publishes a ContextClosedEvent and
	 * destroys the singletons in the bean factory of this application context.
	 * <p>Called by both {
    
    @code close()} and a JVM shutdown hook, if any.
	 * @see org.springframework.context.event.ContextClosedEvent
	 * @see #destroyBeans()
	 * @see #close()
	 * @see #registerShutdownHook()
	 */
	protected void doClose() {
    
    
	....
	try {
    
    
			// Publish shutdown event
			publishEvent(new ContextClosedEvent(this));
		}
		catch (Throwable ex) {
    
    
			logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
		}
	....

根据上述,我们至少知道在SpringBoot程序结束的时候,会通过AbstractApplicationContext#doClose方法发布ContextClosedEvent事件。

AbstractApplicationContext还定义两个方法发布Spring的事件:

@Override
	public void start() {
    
    
		getLifecycleProcessor().start();
		publishEvent(new ContextStartedEvent(this));
	}

	@Override
	public void stop() {
    
    
		getLifecycleProcessor().stop();
		publishEvent(new ContextStoppedEvent(this));
	}

但这两个方法在整个SpringBoot的启动到结束都未被调用。
至此,SpringFramework的事件已经被找出来了。在SpringBoot工程的启停过程,会发布不同的事件,有的属于SpringBoot层面的事件,有的属于Spring层面的事件。当ConfigurableApplicationContext完成refresh后,后面的事件都是通过它来发布。
在这里插入图片描述

猜你喜欢

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