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后,后面的事件都是通过它来发布。