Spring Boot深入原理 - 事件和监听器


Spring Boot深入原理 - SpringApplication启动原理里,我们概括性的介绍了SpringApplication的启动流程。

看过的同学一定知道,第一步就是初始化监听器并触发ApplicationStartedEvent事件。本文就从SpringApplication的第一步开始,深入聊聊Spring Boot的事件和监听器。

Spring Boot事件和监听器类图

分三个部分来说吧:

  • 第一部分是监听器,这里有个ApplicationListener接口,所有监听器都要实现这个接口,包括Spring Boot内置的监听器以及自定义监听器。
  • 第二部分是事件广播器ApplicationEventMulticaster,用于广播事件,触发事件监听器。
  • 第三部分是SpringApplicationRunListener,用于包装事件广播器,并调用事件广播器的广播方法。

其实还有一个比较重要的接口,没在类图中体现:ApplicationEvent,它是所有内置事件和自定义事件的父接口,如ApplicationStartedEvent。

Spring Boot初始化监听器过程

初始化监听器的入口在SpringApplication.run()方法里:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
 //初始化监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
//发布ApplicationStartedEvent
        listeners.starting();
        try {
 //装配参数和环境
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
  //打印Banner
            Banner printedBanner = printBanner(environment);
 //创建ApplicationContext()
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
 //装配Context
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
  //refreshContext
            refreshContext(context);
  //afterRefresh
            afterRefresh(context, applicationArguments);
            //发布ApplicationReadyEvent
  listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
}

进入getRunListeners()方法:

private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
				SpringApplicationRunListener.class, types, this, args));
}

这里直接实例化了一个SpringApplicationRunListeners对象,这里不是重点,重点是里面的getSpringFactoriesInstances():

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
			Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<String>(
				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
				classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
}

这里的核心代码是SpringFactoriesLoader.loadFactoryNames(type, classLoader):

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		try {
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			List<String> result = new ArrayList<String>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
				String factoryClassNames = properties.getProperty(factoryClassName);
				result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
			}
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
					"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

这块是加载监听器的事件广播器的核心逻辑,注意看这里的常量:

/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

进入spring-boot-xxx.RELEASE.jar的META-INF目录下有个spring.factories文件:

这里面定义了大量的接口实现类配置,其中就有本文中重要的org.springframework.boot.context.event.EventPublishingRunListener:

# Run Listeners

org.springframework.boot.SpringApplicationRunListener=\

org.springframework.boot.context.event.EventPublishingRunListener

回到loadFactoryNames方法,这里会加载此文件中的org.springframework.boot.context.event.EventPublishingRunListener并在getSpringFactoriesInstances方法中进行实例化。

通过前面的类图可以看出,EventPublishingRunListener就是事件广播器的包装类。具体广播过程稍后介绍。

到这里好像还没跟SpringApplication的Listeners关联上,不要着急,马上就到了:

public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
}

看到了吧,EventPublishingRunListener的构造函数里,首先创建一个事件广播器对象,然后再将SpringApplication的listeners全部添加到initialMulticaster里。

至此,监听器的初始化就完成了,接下来就看事件是怎么广播出去的。

Spring Boot广播事件过程

监听器的初始化完成以后,就做好了接受事件的准备,那这些事件是怎么广播出去的呢,咱们接着看:

这里拿ApplicationStartedEvent举例(其他事件的广播过程都一样):

首先,在SpringApplication的run()方法里:

listeners.starting();

这个方法里就是遍历所有SpringApplicationRunListener接口的实现类(EventPublishingRunListener)并调用它们的starting方法:

public void starting() {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.starting();
		}
}

在EventPublishingRunListener里,starting方法将会通过事件广播器广播ApplicationStartedEvent事件:

@Override
	@SuppressWarnings("deprecation")
	public void starting() {
		this.initialMulticaster
				.multicastEvent(new ApplicationStartedEvent(this.application, this.args));
	}

广播之后,根据事件类型反向调用对应监听器的onApplicationEvent方法。

    @Override
	public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(new Runnable() {
					@Override
					public void run() {
						invokeListener(listener, event);
					}
				});
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
    protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				listener.onApplicationEvent(event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			try {
				listener.onApplicationEvent(event);
			}
			catch (ClassCastException ex) {
				String msg = ex.getMessage();
				if (msg == null || msg.startsWith(event.getClass().getName())) {
					// Possibly a lambda-defined listener which we could not resolve the generic event type for
					Log logger = LogFactory.getLog(getClass());
					if (logger.isDebugEnabled()) {
						logger.debug("Non-matching event type for listener: " + listener, ex);
					}
				}
				else {
					throw ex;
				}
			}
		}
	}

这个时候如果你实现了ApplicationStartedEvent类型的监听接口:

public class ApplicationStartedListener implements ApplicationListener<ApplicationStartedEvent>

就会触发这个实现类onApplicationEvent方法。其他事件类型的调用过程是一样的,不同的是监听事件类型不一样。

至此,Spring Boot广播事件的全过程就完成了。

总结

本文介绍了Spring Boot事件监听器从初始化到事件广播的全过程。个人在学习的时候,初始化的时候多花了一些时间。但这个过程非常有用,对后续其他部分的理解都非常有帮助。主要还是要沉下心来仔细看看,有个好工具就是debug。

注:本文的目的是介绍事件和监听器的原理,并没有介绍到底有多少监听器。感兴趣的同学可以查阅Spring Boot的docs。

原创文章,转载请注明出处:转载自小马过河 - Spring Boot深入原理 - 事件和监听器


Jbone

Spring Cloud实战项目jbone正在开发中, jbone功能包括服务管理、单点登录、系统管理平台、内容管理平台、电商平台、支付平台、工作流平台等子系统。欢迎关注!

我要关注
马军伟
关于作者 马军伟
写的不错,支持一下

先给自己定个小目标,日更一新。