一文看懂spring事件监听器

spring基于发布订阅模式实现了事件监听器,用于处理启动中的所有事件,可以对功能按模块解耦。首先spring启动时会从spring.factories中读取配置的监听器,先看下这个过程。

java 复制代码
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    ...
        //获取监听器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    ...
	}

读取所有jar包下的META-INF/spring.factories,根据ApplicationListener的全路径名获取对应的接口实现类,缓存下来后再返回ApplicationListener的定制实现类,然后以反射的方式构造实例对象。spring中大量运用了缓存,避免过多的内存分配造成的cpu无效占用。

java 复制代码
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// 根据接口名获取实现类名
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 反射构造实例对象
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}   

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        //优先从缓存中获取
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
            //读取所有jar包下META-INF/spring.factories配置文件
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
                //加载key=value配置文件
                //key为接口全路径名,value为接口的实现类
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}    

run方法运行容器时会发布各种事件,首先跟读取监听器的方式类似,获取事件发布器,用于发布各阶段事件,此时会将从spring.factories中读取的事件监听器加入事件发布器,让支持的事件监听器去处理,接下来我们看spring启动过程中发布的第一个事件ApplicationStartingEvent启动事件。

java 复制代码
	public ConfigurableApplicationContext run(String... args) {
...         //从spring.factories读取事件发布器SpringApplicationRunListener
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
...
	}

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
        //spring的事件监听器添加到广播,即事件发布器
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

获取支持处理ApplicationStartingEvent事件的监听器,然后处理,接下来重点分析这个过程。

java 复制代码
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

spring会按事件保存到ListenerRetriever内置的集合Set<ApplicationListener<?>> applicationListeners,同时将事件和ListenerRetriever缓存到Map<ListenerCacheKey, ListenerRetriever> retrieverCache中,用于快速检索,发现没获取到就会按事件过滤retrieveApplicationListeners,找出支持处理事件的监听器,接下来以DelegatingApplicationListener为例看懂这个过程,getApplicationListeners会过滤出能够处理ApplicationStartingEvent事件的DelegatingApplicationListener。

java 复制代码
	protected Collection<ApplicationListener<?>> getApplicationListeners(
			ApplicationEvent event, ResolvableType eventType) {

		Object source = event.getSource();
		Class<?> sourceType = (source != null ? source.getClass() : null);
		ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

		// 缓存中根据事件类型和事件对象快速检索
		ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
		if (retriever != null) {
			return retriever.getApplicationListeners();
		}

		if (this.beanClassLoader == null ||
				(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
						(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
			// Fully synchronized building and caching of a ListenerRetriever
			synchronized (this.retrievalMutex) {
				retriever = this.retrieverCache.get(cacheKey);
				if (retriever != null) {
					return retriever.getApplicationListeners();
				}
				retriever = new ListenerRetriever(true);
                //获取不到即需要手动解析,从所有监听器中找出支持事件的监听器
				Collection<ApplicationListener<?>> listeners =
						retrieveApplicationListeners(eventType, sourceType, retriever);
				this.retrieverCache.put(cacheKey, retriever);
				return listeners;
			}
		}
		else {
			// No ListenerRetriever caching -> no synchronization necessary
			return retrieveApplicationListeners(eventType, sourceType, null);
		}
	}

retrieveApplicationListeners方法中会按事件过来监听器,ResolvableType eventTypeApplicationEvent的封装,Class<?> sourceType为ApplicationEvent中携带的属性,ListenerRetriever retriever为单一事件集合,方法中会通过supportsEvent方法找出处理ApplicationStartingEvent事件的DelegatingApplicationListener

java 复制代码
	private Collection<ApplicationListener<?>> retrieveApplicationListeners(
			ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

		List<ApplicationListener<?>> allListeners = new ArrayList<>();
......
		for (ApplicationListener<?> listener : listeners) {
			if (supportsEvent(listener, eventType, sourceType)) {
				if (retriever != null) {
					retriever.applicationListeners.add(listener);
				}
				allListeners.add(listener);
			}
		}
......
		return allListeners;
	}

继续看supportsEvent方法,这里用到了适配器模式,通过GenericApplicationListenerAdapter适配器去找能够处理ApplicationStartingEvent事件的监听器,这里的ApplicationListener<?> delegat就是DelegatingApplicationListener,现在看resolveDeclaredEventType如何找出DelegatingApplicationListener支持的事件类型。

java 复制代码
	protected boolean supportsEvent(
			ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {

		GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
				(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
		return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
	}

	public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
		Assert.notNull(delegate, "Delegate listener must not be null");
		this.delegate = (ApplicationListener<ApplicationEvent>) delegate;
		this.declaredEventType = resolveDeclaredEventType(this.delegate);
	}	

还是一样的,先根据事件ApplicationStartingEvent到缓存中检索相应的监听器,查无再进行处理。分三步:

  1. ResolvableType封装监听器Class
  2. 找到监听器Class实现的ApplicationListener接口
  3. 从实现的ApplicationListener接口获取监听器支持的事件类型,即接口中声明的泛型类型,public class DelegatingApplicationListener implements ApplicationListener<ApplicationEvent>, OrderedApplicationEvent意味着可以处理所有事件。
java 复制代码
	static ResolvableType resolveDeclaredEventType(Class<?> listenerType) {
		ResolvableType eventType = eventTypeCache.get(listenerType);
		if (eventType == null) {
			eventType = ResolvableType.forClass(listenerType).as(ApplicationListener.class).getGeneric();
			eventTypeCache.put(listenerType, eventType);
		}
		return (eventType != ResolvableType.NONE ? eventType : null);
	}

之后就是调用每个监听器的方法onApplicationEvent处理事件,能够处理ApplicationStartingEvent事件的监听器:

  1. LoggingApplicationListener-初始化日志系统
  2. BackgroundPreinitializer-没做具体处理
  3. DelegatingApplicationListener-没做具体处理
  4. LiquibaseServiceLocatorApplicationListener-没做具体处理

总结下,springboot启动时会读取spring.factories配置的监听器,启动时会发布事件,然后选择对应的监听器进行处理,以实现功能的解耦同时做到高扩展性。有不对的地方请大神指出,欢迎大家一起讨论交流,共同进步,更多请关注微信公众号 葡萄开源

相关推荐
刘小吉1 分钟前
java net 配置局域网受信任的https
后端
coolflyr_reg18 分钟前
禅道集成Firebase PHP-JWT
后端
似水流年流不尽思念21 分钟前
常见的排序算法有哪些?它们的平均时间复杂度是多少?
后端·算法
孟永峰_Java1 小时前
MySQL 组合IN查询:你的索引为什么罢工了?
后端
ruokkk1 小时前
一个困扰我多年的Session超时Bug,被我的新AI搭档半天搞定了
javascript·后端·架构
楽码1 小时前
端到端应用Hmac加密
服务器·后端·算法
孟永峰_Java1 小时前
Java程序员的周五:代码没写完,但我的心已经放假了!
后端
uhakadotcom1 小时前
Flink有python的SDK入门教程
后端·面试·github
kong@react2 小时前
spring boot配置es
spring boot·后端·elasticsearch