SpringBoot源码分析(三):SpringBoot的事件分发机制

这篇具有很好参考价值的文章主要介绍了SpringBoot源码分析(三):SpringBoot的事件分发机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

通过源码明晰的几个问题

通过解读 Springboot 的事件分发源码,了解一下几个问题:

  1. Springboot 注册事件监听器有几种方式,分别是什么?
  2. 什么情况下注册的事件监听会失效(接收不到Springboot事件)?
  3. Springboot 利用的哪一个类做的事件分发操作?
  4. Spring 是如何利用泛型做到的事件分发?
  5. 怎么自定义 listener 监听多个类型事件?
  6. 除了Spring或Springboot自己提供的事件,怎么自定义事件?

这些问题将会在看源码的过程中一一清晰,在文末会总结这几个问题的答案。

源码分析中为避免代码过多,会忽略无关紧要的代码。

Spring 中的事件

在 Springboot 中要监听 Spring 中的事件,需要实现 ApplicationListener 这个接口,这个接口不是 Springboot 提供的,而是 Spring 提供的。
在代码中使用步骤如下:

@Component
public class TestBean implements ApplicationListener<ApplicationStartedEvent> {

	@Override
	public void onApplicationEvent(ApplicationStartedEvent event) {
		// 处理 ApplicationStartedEvent 事件
	}
}

这里可以看到,通过实现 ApplicationListener 监听到了 ApplicationStartedEvent 事件,这个事件将会在 Springboot 应用启动成功后接收到。而 ApplicationStartedEvent 这个事件并不是 Spring 提供的,而是Springboot提供的,这个很重要,能理解这个区别后面自定义事件就好理解了。

Springboot 是怎么做到事件监听的

另外两种注册的Listener

  • SPI(spring.factories)方式
    除了上面通过 bean 方式注册事件监听器,还有两种方式注册事件监听器。一种是我们通过SPI定义在 spring.factories 文件中的 listener。
    具体原理可与看我之前的文章。
    Springboot 的 spring.factories 文件中定义了下面几种listener, Springboot 就是通过这种 listener 完成的各种Spring的各种加载配置。
# Application Listeners  
org.springframework.context.ApplicationListener=\  
org.springframework.boot.ClearCachesApplicationListener,\  
org.springframework.boot.builder.ParentContextCloserApplicationListener,\  
org.springframework.boot.context.FileEncodingApplicationListener,\  
org.springframework.boot.context.config.AnsiOutputApplicationListener,\  
org.springframework.boot.context.config.DelegatingApplicationListener,\  
org.springframework.boot.context.logging.LoggingApplicationListener,\  
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener
  • 通过程序注册
    另一种方式可能听过的比较少,在Springboot程序启动之前是可以设置事件监听的。
@SpringBootApplication  
public class SpringDemoApplication {
	ConfigurableApplicationContext run = new SpringApplicationBuilder(SpringDemoApplication.class) 
	// 使用SpringApplicationBuilder启动Springboot应用,调用 listeners 函数手动设置监听器
	.listeners(new CustomListener()).run(args);
	
	// 或者 创建 SpringApplication 对象,调用 addListeners 函数手动设置监听器
	SpringApplication application =  new SpringApplicationBuilder(SpringDemoApplication.class).build(args);
	application.addListeners(new CustomListener());
	ConfigurableApplicationContext run = application.run();
}

源码解析

那么 Springboot 是怎么做到这种方式的事件分发呢?具体来看源码。

加载listener

在Springboot的启动类的构造函数中,通过SPI方式加载了配置在SPI配置文件中的 listener

SpringApplicaton

private List<ApplicationListener<?>> listeners;

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	//从配置文件中加载事件列表
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	//...
}

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {  
	this.listeners = new ArrayList<>(listeners);  
}

// 程序手动添加listener最终都会调用这个方法
public void addListeners(ApplicationListener<?>... listeners) {
	this.listeners.addAll(Arrays.asList(listeners));
}

需要注意的是,这里收集的只是后面提到的两种listener,通过bean注册的并没有被收集到,为什么,因为Bean需要bean容器启动才能收集到,Bean容器还没有启动和准备好,怎么可能收集到呢?这里只是收集到了listener,怎么做的分发呢。

SpringApplicationRunListener

在Springboot中提供了接口,SpringApplicationRunListener

  • 它是一个接口
  • 该接口提供了整个 SpringBoot 生命周期的回调函数
  • 实现这个接口,必须提供一个 (SpringApplication, String[]) 的构造函数, 其中SpringApplication 就是 SpringBoot 的启动类, String[] 提供的是命令行参数。

Springboot 就是通过它做的事件分发,同样这个接口也是通过SPI进行注册的。

SpringAppliction

public ConfigurableApplicationContext run(String... args) {
	// 获取 SpringApplicationRunListeners 这个 listeners 是一个包装类,里面包装了所有从配置文件中读取到的 SpringApplicationRunListener 集合
	// 通过这个 listeners 调用 SpringApplicationRunListener 的对应方法,同时进行一些日志记录、运行时间记录等操作
	SpringApplicationRunListeners listeners = getRunListeners(args);
	// 调用 listeners 的 staring 方法
	listeners.starting(bootstrapContext, this.mainApplicationClass);
	try {
		// 在prepareEnvironment 中调用了 listeners.environmentPrepared()
		ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
		// 在 prepareContext 中调用了 listeners.contextPrepared() 和 contextLoaded()
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		// 启动成功后 调用 started()
		listeners.started(context, timeTakenToStartup);
	} catch (Throwable ex) {
		// 执行失败调用 listeners 的 failed 方法
		handleRunFailure(context, ex, listeners);
	}
	try {
		// springboot 应用准备完毕后调用 ready()
		listeners.ready(context, timeTakenToReady);
	}
	return context;
}

private SpringApplicationRunListeners getRunListeners(String[] args) {
	// 这里说明 SpringApplicationRunListener 必须提供 SpringApplication, String[] 的构造函数
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	// 从配置文件中读取所有的 SpringApplicationRunListener 封装进  SpringApplicationRunListeners 包装类中
	return new SpringApplicationRunListeners(logger,
			getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
			this.applicationStartup);
}
回调函数 说明 对应的listener
starting(context) 在Springboot程序启动时被调用 ApplicationStartingEvent
environmentPrepared(context,environment) 在Springboot环境准备好后被调用,也就是说各种配置读取完成后被调用 ApplicationEnvironmentPreparedEvent
contextPrepared(context) 在Springboot上下文准备好后被调用,就是各种Bean还没有加载完成 ApplicationContextInitializedEvent
contextLoaded(context) 在Springboot上下文加载完成后被调用,这时候Bean都已经被加载完成了 ApplicationPreparedEvent
started(context, duration) 在Springboot启动后CommandLineRunner和ApplicationRunner被调用前被调用 ApplicationStartedEvent
ready(context, duration) 在CommandLineRunner 和 ApplicationRunner 调用后被调用 ApplicationReadyEvent
failed(context, throwable) 启动失败后被调用 ApplicationFailedEvent

从上面就可以看到,Springboot 启动中的各种事件就是通过读取配置文件中的 SpringApplicationRunListener 完成的,具体是哪一个类呢?

EventPublishingRunListener

在spring.factories 中能看到这么一个唯一的实现类:EventPublishingRunListener

# Run Listeners  
org.springframework.boot.SpringApplicationRunListener=\  
org.springframework.boot.context.event.EventPublishingRunListener

从名字就能看出来,它就是做事件分发的

EventPublishingRunListener

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
	private final SimpleApplicationEventMulticaster initialMulticaster;

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		// 把application中收集到的listener设置给SimpleApplicationEventMulticaster
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {
		// 分发事件 ApplicationStartingEvent
		this.initialMulticaster
			.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
	}

	@Override
	public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
		// 分发事件 ApplicationEnvironmentPreparedEvent
		this.initialMulticaster.multicastEvent(
				new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		// 分发事件 ApplicationContextInitializedEvent
		this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			// 把application的listener添加的 Context 中、
			// 这个事件列表,加上context(bean容器)中的listener就是所有listener了
			context.addApplicationListener(listener);
		}
		// 分发事件 ApplicationPreparedEvent
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

	@Override
	public void started(ConfigurableApplicationContext context, Duration timeTaken) {
		// 这里不同了,在 context 已经加载完毕的时候就可以使用 context 来分发事件了
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
	}

	@Override
	public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
		// 在 context 已经加载完毕的时候就可以使用 context 来分发事件了
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
	}

	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
		// 出现失败可能在Springboot的任意启动过程中,所以要判断 context 是否已经加载完毕,加载完毕使用 context 分发事件
		if (context != null && context.isActive()) {
			context.publishEvent(event);
		} else {
			// 省略部分代码... 未加载完毕使用 默认的方式分发事件			
			this.initialMulticaster.multicastEvent(event);
		}
	}
}

从代码中可以看到几点:

  1. 通过调用 SimpleApplicationEventMulticaster 的 multicastEvent 方法完成的事件分发
  2. 在 context 容器加载完毕之后 (contextLoaded) 之后,使用 context.publishEvent 进行事件分发了,因为这时候所有的事件都已经被收集到 context 中了 (包括SPI注册的,Bean 注册的)
  3. 在启动失败时,会判断 context 是否加载完成决定使用哪种方式进行处理。那么也就说明,我们在代码中通过Bean方式注册的 ApplicationFailedEvent 只会在 Context 加载完成后起作用。

那么这里就可以看出来,什么情况下配置的listener会不起作用呢?有两个条件:

  1. listener 是通过Bean注册的
  2. 在Context加载完成之前
    因为 Bean 在 contextLoaded 之后才会被加载进入 Springboot 容器中,所以通过这种方式只能注册下面这三种 listener
    1. ApplicationStartedEvent
    2. ApplicationReadyEvent
    3. ApplicationFailedEvent

SimpleApplicationEventMulticaster

无论是 SPI 的事件分发,还是 Bean 方式的事件分发,都是依赖 SimpleApplicationEventMulticaster 来完成的。通过调用函数 publishEvent 进行事件分发。

  • 该类是 Spring 提供的,而不是Springboot 提供的。
  • 从名字可以看出来,它是一个事件广播器,把事件广播给所有注册该事件的 listener
  • 该类提供了 setExecutor 方法,可以让事件分发通过线程池多线程方式执行,但是Springboot的事件分发中并没有使用。

SimpleApplicationEventMulticaster

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		// 查找到符合该类型的 listener
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			// 如果线程池存在,使用线程池执行 listener
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			} else {
				// 如果线程池不存在,直接调用 listener
				invokeListener(listener, event);
			}
		}
	}

	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			} catch (Throwable err) {
				// 如果有异常处理器,调用异常处理器逻辑(这部分在 SpringApplicationRunListener failed() 方法中有用到,用于打印日志)
				errorHandler.handleError(err);
			}
		} else {
			doInvokeListener(listener, event);
		}
	}

	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			// 打印日志或 向上抛出异常,省略该部分代码...
		}
	}
}

上面的整个事件分发的处理逻辑比较简单,关键是,怎么找到该事件对应的 listeners 呢?
下面的流程源码的流程大致是:

  • 根据 event 的类型和event对应的source类型进行缓存
  • 从缓存总查找对应的listener,如果缓存中不存在,从全部listener中过滤出来需要的listener,放入缓存中
  • 这里的listener就分为了两种,通过spi和程序指定的listener因为不会被修改,为一类,直接放入缓存的listener列表中,非单例的bean会因scope的不同每次获取不同的实例,所以通过bean名称进行缓存,获取时通过beanFactory 进行获取。

SimpleApplicationEventMulticaster

protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {

	Object source = event.getSource();
	// Spring 中的 Event 都会绑定一个 source, 也就是说 source + eventType 就可以唯一确定一类事件
	// 所以事件分发的时候,也就是要找到符合这一条件的事件类型的listener
	Class<?> sourceType = (source != null ? source.getClass() : null);
	// 这里使用了缓存来缓存事件对象
	ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

	// 新旧 retriever 的查询逻辑,这里写这么复杂的原因应该是怕多线程情况下两端代码同时走到这一段
	CachedListenerRetriever newRetriever = null;
	CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);

	if (existingRetriever == null) {
		// 这里判断 event 和 source 是否和 beanClassLoader 在同一个 classLoader 下
		if (this.beanClassLoader == null ||
				(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
						(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
			newRetriever = new CachedListenerRetriever();
			// 这里应该就是检查一下防止其他线程已经设置了
			existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
			// 如果已经被其他线程设置了,那么用以前线程的缓存对象
			if (existingRetriever != null) {
				newRetriever = null;  // no need to populate it in retrieveApplicationListeners
			}
		}
	}

	if (existingRetriever != null) {
		// 能进来这里说明有两种情况
		// 1. 根据cacheKey找到了对应的事件列表
		// 2. 有其他线程在设置 cacheKey 之前设置了
		Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
		if (result != null) {
			return result;
		}
		// result为空,说明其他线程有可能还没有完成事件的过滤填充,所以进行下面的流程
	}
	// 根据 retriever 过滤 listener
	return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
		ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {

	// listener 分为两种类型,一种就是通过 SPI 注册的 listener,在 Application 启动的时候就加载出来的
	// 一种是 Bean, 在容器准备好之后可以通过 beanName 从 beanFactory 中拿到
	
	// 这里有两个 listener 列表的原因是: 
	// allListener 中包括了 beanListener 和 spiListener
	// 而 filteredListeners 只有 spiListener
	List<ApplicationListener<?>> allListeners = new ArrayList<>();
	Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);

	// beanListener 列表
	Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);

	// 所有的 listener 列表, defaultRetriever 缓存了所有的 listener
	Set<ApplicationListener<?>> listeners;
	Set<String> listenerBeans;
	synchronized (this.defaultRetriever) {
		listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
		listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
	}

	// 扫描符合要求的 spiListener
	for (ApplicationListener<?> listener : listeners) {
		// 判断是否符合
		if (supportsEvent(listener, eventType, sourceType)) {
			if (retriever != null) {
				filteredListeners.add(listener);
			}
			allListeners.add(listener);
		}
	}

	// 扫描所有的 beanListener
	if (!listenerBeans.isEmpty()) {
		ConfigurableBeanFactory beanFactory = getBeanFactory();
		for (String listenerBeanName : listenerBeans) {
			try {
				// 判断该 beanListener 是否符合 
				if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
					ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
					// 避免重复
					if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
						if (retriever != null) {
							// 如果是单例的,添加到 spiListener 中,如果不是,添加到 filteredListenerBeans 中
							// 这里是一个优化的点,为什么呢?
							// Spring 中默认的Bean是单例的,所以对象被容器创建出来之后对象引用就不会改变了,所以这里把它加到 filteredListeners 中
							// 而对于非单例的bean,不同的环境中可能对象会变化,所以这里把他添加到 beans 中,每次获取的时候通过 beanFactory 获取,而不进行缓存
							if (beanFactory.isSingleton(listenerBeanName)) {
								filteredListeners.add(listener);
							} else {
								filteredListenerBeans.add(listenerBeanName);
							}
						}
						allListeners.add(listener);
					}
				}
				else {
					// 不支持的bean,从listener 中移除
					Object listener = beanFactory.getSingleton(listenerBeanName);
					if (retriever != null) {
						filteredListeners.remove(listener);
					}
					allListeners.remove(listener);
				}
			} catch (NoSuchBeanDefinitionException ex) {
			}
		}
	}

	AnnotationAwareOrderComparator.sort(allListeners);

	if (retriever != null) {
		// 如果不存在 beanListener,也就是说所有的listener都是 spi方式注册的或者是单例的
		//(这里其实用 allListener 或者用 filteredListeners效果是一样的)
		if (filteredListenerBeans.isEmpty()) {
			retriever.applicationListeners = new LinkedHashSet<>(allListeners);
			retriever.applicationListenerBeans = filteredListenerBeans;
		}
		else {
			retriever.applicationListeners = filteredListeners;
			retriever.applicationListenerBeans = filteredListenerBeans;
		}
	}
	// 如果 retriever 为空,返回所有的 listener
	return allListeners;
}

SimpleApplicationEventMulticaster$CachedListenerRetriever

private class CachedListenerRetriever {

	// 对象引用不变的listener
	@Nullable
	public volatile Set<ApplicationListener<?>> applicationListeners;
	// 非单例beanlistener(对象引用可能会变)
	@Nullable
	public volatile Set<String> applicationListenerBeans;

	@Nullable
	public Collection<ApplicationListener<?>> getApplicationListeners() {
		Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;
		Set<String> applicationListenerBeans = this.applicationListenerBeans;
		if (applicationListeners == null || applicationListenerBeans == null) {
			// Not fully populated yet
			return null;
		}
		// 两个合起来才是最终结果
		List<ApplicationListener<?>> allListeners = new ArrayList<>(
				applicationListeners.size() + applicationListenerBeans.size());
		
		allListeners.addAll(applicationListeners);
		
		if (!applicationListenerBeans.isEmpty()) {
			BeanFactory beanFactory = getBeanFactory();
			for (String listenerBeanName : applicationListenerBeans) {
				try {
					allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class));
				}
				catch (NoSuchBeanDefinitionException ex) {
				}
			}
		}
		//... 
		return allListeners;
	}
}

判断 listener 是否可以接收事件

具体是怎么判断 listner 是否支持 listener ?就需要看下面的代码了。

/**
 * 推断 listener 是否符合要求
 */
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));
}

从这里可以看到,主要是通过 GenericApplicationListener, 的两个方法判断是否支持 eventType 和 sourceType的。

  • GenericApplicationListener 继承了 SmartApplicationListener

只要实现了这个接口中的这两个方法就能决定 listener 是否支持那些事件了。

但是,我们程序里面写的 listener 其实并没有 实现这两个接口,那是怎么判断的呢?从代码来看,不属于 GenericApplicationListener 的,利用适配器模式包装成了 GenericApplicationListener。也就是 new GenericApplicationListenerAdapter(listener);

public class GenericApplicationListenerAdapter implements GenericApplicationListener {

	public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
		this.delegate = (ApplicationListener<ApplicationEvent>) delegate;
		// 获取
		this.declaredEventType = resolveDeclaredEventType(this.delegate);
	}

	@Nullable
	private static ResolvableType resolveDeclaredEventType(ApplicationListener<ApplicationEvent> listener) {
		// 获取事件类型,如果无法获取,判断是否是代理类,从代理类获取
		ResolvableType declaredEventType = resolveDeclaredEventType(listener.getClass());
		if (declaredEventType == null || declaredEventType.isAssignableFrom(ApplicationEvent.class)) {
			Class<?> targetClass = AopUtils.getTargetClass(listener);
			if (targetClass != listener.getClass()) {
				declaredEventType = resolveDeclaredEventType(targetClass);
			}
		}
		return declaredEventType;
	}

	@Nullable
	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);
	}

	public boolean supportsEventType(ResolvableType eventType) {
		if (this.delegate instanceof GenericApplicationListener) {
			return ((GenericApplicationListener) this.delegate).supportsEventType(eventType);
		}
		else if (this.delegate instanceof SmartApplicationListener) {
			Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.resolve();
			return (eventClass != null && ((SmartApplicationListener) this.delegate).supportsEventType(eventClass));
		}
		// 上面两行代码都是一样的,没什么可说的 直接调用的扩展方法 supportsEventType 进行判断
		// 下面这一行代码是根据泛型进行判断的
		else {
			return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
		}
	}
}

上面可以看到,通过 ResolvableType.forClass(listenerType).as(ApplicationListener.class).getGeneric(); 获取到类的泛型。

最后再判断 eventType 是否属于 listener的泛型类型 declaredEventType, 这样基于能拿到具体进行匹配了。

作为扩展,看一下 ResolvableType 是怎么拿到泛型的。

ResolvableType

public ResolvableType[] getGenerics() {
	// 缓存
	ResolvableType[] generics = this.generics;
	if (generics == null) {
		if (this.type instanceof Class) {
			Type[] typeParams = ((Class<?>) this.type).getTypeParameters();
			generics = new ResolvableType[typeParams.length];
			for (int i = 0; i < generics.length; i++) {
				generics[i] = ResolvableType.forType(typeParams[i], this);
			}
		}
		else if (this.type instanceof ParameterizedType) {
			Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments();
			generics = new ResolvableType[actualTypeArguments.length];
			for (int i = 0; i < actualTypeArguments.length; i++) {
				generics[i] = forType(actualTypeArguments[i], this.variableResolver);
			}
		}
		else {
			generics = resolveType().getGenerics();
		}
		this.generics = generics;
	}
	return generics;
}

Java 泛型获取

  • class.getTypeParameters 获取直接定义在接口或类上的泛型
interface A<T, E, V> {}
// A getTypeParameters [T, E, V]
System.out.println("A getTypeParameters " + Arrays.toString(A.class.getTypeParameters()));
  • type.getActualTypeArguments 获取类实际上的泛型,包括继承、实现的父类中的泛型
interface A<T, E, V> {}  
  
interface B<T, V> {}

static class E implements B<Integer, Double>, A<ApplicationContext, Double, Integer> {}

// E:interface B getActualTypeArguments [class java.lang.Integer, class java.lang.Double]
// E:interface A getActualTypeArguments [interface org.springframework.context.ApplicationContext, class java.lang.Double, class java.lang.Integer]
testGetActualTypeArguments(E.class);

private static void testGetActualTypeArguments(Class<?> clz) {
	final Type[] types = clz.getGenericInterfaces();
	for (Type type : types) {
		if (type instanceof ParameterizedType) {
			ParameterizedType typeP = ((ParameterizedType) type);
			System.out.println(clz.getSimpleName() + ":" + typeP.getRawType()
					+ " getActualTypeArguments " + Arrays.toString(typeP.getActualTypeArguments()));
		} else {
			System.out.println(clz.getSimpleName() + " is not a parameterized type");
		}
	}
}

整体流程回顾

Springboot事件分发流程

  • SpringBoot 启动时通过加载 SPI(ApplicationListener) 加载到配置文件中的监听事件
  • 加载 SPI(SpringApplicationRunListener),加载 EventPublishRunListener, 通过 SpringApplicationRunListener 在Springboot不同的生命周期中调用对应的 listener
    • EventPublishRunListener 通过 SimpleApplicationEventMulticaster 进行事件的分发
    • 程序启动时,将SPI和程序中通过Application添加的listener赋值给 SimpleApplicationEventMulticaster,通过它进行事件分发
    • 容器准备完成后,容器会注册一个 SimpleApplicationEventMulticaster,把程序启动时的listener传递给context,context传递给SimpleApplicationEventMulticaster。
    • 此时,容器中注册的这个SimpleApplicationEventMulticaster包含了所有的listener,此时,就可以通过context进行事件的分发了。

SimpleApplicationEventMulticaster 事件分发流程

  • 根据eventType(事件类) + sourceType(source类)检查缓存
  • 缓存中存在已经过滤的事件列表,直接返回
  • 缓存中不存在过滤的事件列表进行过滤,缓存
    • 根据 GenericApplicationListener 的两个方法判断listener是否支持该事件
    • 如果listener没有实现GenericApplicationListener接口,则根据泛型的类型判断是否支持该listener

问题解答

至此,整个事件分发的流程也就分析结束了,那么上面的几个问题也就有答案了

1. Springboot 注册事件监听器有几种方式,分别是什么?

有三种方式

  • 通过SPI方式,在 spring.factories 中定义 ApplicationListener 进行注册
  • 通过创建 SpringApplication 的时候手动添加
  • 通过注册Bean实现ApplicationListener方式添加

2. 什么情况下注册的事件监听会失效(接收不到Springboot事件)?

通过Bean方式注册的事件,并且属于容器加载完成前的时间都接收不到,因为通过Bean方式注册的事件监听只能监听到容器加载后的事件。

3. Springboot 利用的哪一个类做的事件分发操作?

Springboot 利用的 EventPublishRunListener 完成的 Springboot 声明周期的事件分发,底层利用的是 SimpleApplicationEventMulticaster

4. Spring 是如何利用泛型做到的事件分发?

Spring 利用 ResolvableType 类获取类的泛型参数,根据该泛型参数判断接收到的事件是否符合该泛型判断该 listener 是否可以处理该事件。

5. 怎么自定义 listener 达到监听多个类型事件?

从源码中可以看到,如果我们制定了具体的事件类型,就只能监听这种类型的事件,比如

public class TestBean implements ApplicationListener<ApplicationStartedEvent> {}

这种方式就只能监听到 ApplicationStartedEvent 这种事件。

要监听多个事件类型有两种方式。

  1. 泛型中如果写 ApplicationEvent,那么就能监听到所有继承了这个类的事件了。
  2. 实现 SmartApplicationListener 接口,通过重写 supportsEventType(Class<? extends ApplicationEvent> event) 自定义接收的时间类型逻辑。

6. 除了Spring或Springboot自己提供的事件,怎么自定义事件?

其实通过上面的代码,已经看出来,Springboot 也只是利用了 Spring 的事件机制完成的事件分发,Springboot 利用它完成了Springboot 声明周期事件的分发,但是,实际上 Spring 并没有规定只能接收到 Springboot 规定的这些个事件。

仿照Springboot的发布事件方式,我们完全可以定义自己的事件。

比如我们自定义的事件名称为 CustomEvent, 继承ApplicationEvent,如下:

public class CustomEvent extends ApplicationEvent {

    private final String msg;

    public CustomEvent(Object source, String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }
}

那么,bean可以实现 ApplicationListener接口来监听这个事件

@Component
public class TestCustomEventComponent implements ApplicationListener<CustomEvent> {
    @Override
    public void onApplicationEvent(CustomEvent event) {
        System.out.println("我收到自定义的时间啦:" + event.getMsg());
    }
}

在需要通信的地方,发送这个事件

final ApplicationEventMulticaster eventMulticaster = context.getBean(ApplicationEventMulticaster.class);  
eventMulticaster.multicastEvent(new CustomEvent("", "我是一个自定义的事件"));

这样就能接收到事件了。

那么这么做有什么好处呢?
这里举一个例子,假如有一个 service ,用户执行完成后需要调用短信服务发送短信,需要记录日志。正常情况下可能会这样写。

@Autowire
private SmsService smsService;
@Autowire
private LogService logService;

public void handle() {
	// do some thing
	smsService.sendSms(...);
	logService.recordLog(...);
}

这时候就可以看到, 这个 service 强依赖与另外两个服务。假如将来服务做拆分,改动量就比较大了。

如果用事件分发怎么做呢,可以自定义一个Event,就比如 CommonEvent, 短信服务和日志服务都监听这个 Event
那么上面代码就变成了:

@Autowire
private  ApplicationEventMulticaster eventMulticaster;
public void handle() {
	// do some thing
	eventMulticaster.multicastEvent(new CommonEvent(...))
}

这样 Sercvice 之间代码逻辑就解耦了,怎么样是不是感觉有那么一点点眼熟?没错,项目之间通过消息队列进行解耦和这个这不是特别相似吗?

最后

至此Springboot事件分发源码算是梳理完了,梳理过程中才发现一个小小的事件分发居然会有这么多的细节,以前都没有注意到,比如说什么情况下listener会失效,怎么自定义监听的事件类型,怎么自定义事件等等,看源码的过程不仅仅是学习优秀框架的方式,也是对Spring或者Springboot这个框架进一步加深理解的机会。

前些日子试了一下ai上色,还挺好玩的,附一张最爱的云堇图片。
SpringBoot源码分析(三):SpringBoot的事件分发机制文章来源地址https://www.toymoban.com/news/detail-499768.html

到了这里,关于SpringBoot源码分析(三):SpringBoot的事件分发机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 带你深入了解Android的事件分发机制

    Android的事件分发机制是指在Android系统中,如何将用户的触摸事件、按键事件等传递给正确的View进行处理的一套机制。它是Android应用程序中实现交互的重要部分,确保用户的操作能够被正确地捕获和处理。 Android的事件分发机制涉及到以下几个核心概念:事件源、事件分发、

    2024年02月16日
    浏览(33)
  • Android事件分发机制五:面试官你坐啊

    学过事件分发吗,聊聊什么是事件分发 事件分发是将屏幕触控信息分发给控件树的一个套机制。 当我们触摸屏幕时,会产生一些列的MotionEvent事件对象,经过控件树的管理者ViewRootImpl,调用view的dispatchPointerEvnet方法进行分发。 那主要的分发流程是什么: 在程序的主界面情况

    2024年04月15日
    浏览(43)
  • spring 用户通过交互界面登录成功事件源码分析

    spring-security-web:5.6.7 用户通过前端交互界面登录成功触发此事件 org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent 事件触发过程 用户名密码认证过滤器 org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 认证处理过滤器 org.springframework.security.we

    2024年02月16日
    浏览(38)
  • Flutter中为控件添加交互,带你一起探究Android事件分发机制

    ), ); } } 代码运行效果如图: 2.父widget管理widget的状态 对于父widget来说,管理状态并告诉其子widget何时更新通常是最有意义的。 例如,IconButton允许您将图标视为可点按的按钮。 IconButton是一个无状态的小部件,因为我们认为父widget需要知道该按钮是否被点击来采取相应的处理

    2024年04月11日
    浏览(44)
  • Android事件分发-基础原理和场景分析

    作者:京东零售 郭旭锋 和其他平台类似,Android 中 View 的布局是一个树形结构,各个 ViewGroup 和 View 是按树形结构嵌套布局的,从而会出现用户触摸的位置坐标可能会落在多个 View 的范围内,这样就不知道哪个 View 来响应这个事件,为了解决这一问题,就出现了事件分发机制

    2023年04月21日
    浏览(41)
  • SpringBoot源码解读与原理分析(五)SpringBoot的装配机制

    前面三小节分别介绍了Spring Framewoek的模块装配、条件装配和SPI机制。下面正式进入Spring Boot的装配机制。 实际上, Spring Boot的自动装配是模块装配+条件装配+SPI机制的组合使用 ,而这一切都凝聚在Spring Boot主启动类的@SpringBootApplication注解上。 @SpringBootApplication注解是由三个注

    2024年02月19日
    浏览(48)
  • Android进阶 View事件体系(二):从源码解析View的事件分发

    本篇文章为总结View事件体系的第二篇文章,前一篇文章的在这里:Android进阶 View事件体系(一):概要介绍和实现View的滑动 本篇文章将专注于介绍View的点击事件的分发,介绍的内容主要有: 点击事件的传递原则 解析Activity的构成 源码解析View的事件分发 源码解析View对点击

    2024年02月06日
    浏览(46)
  • Android-高级-UI-进阶之路-(二)-深入理解-Android-8-0-View-触摸事件分发机制,查漏补缺

    我们看到内部又调用了父类 dispatchTouchEvent 方法, 所以最终是交给 ViewGroup 顶级 View 来处理分发了。 顶级 View 对点击事件的分发过程 在上一小节中我们知道了一个事件的传递流程,这里我们就大致在回顾一下。首先点击事件到达顶级 ViewGroup 之后,会调用自身的 dispatchTouchE

    2024年04月14日
    浏览(68)
  • Android OkHttp源码分析--分发器

    OkHttp是当下Android使用最频繁的网络请求框架,由Square公司开源。Google在Android4.4以后开始将源码中 的HttpURLConnection底层实现替换为OKHttp,同时现在流行的Retrofit框架底层同样是使用OKHttp的。 OKHttp优点: 1、支持Http1、Http2、Quic以及WebSocket; 2、连接池复用底层TCP(Socket),减少请求

    2024年02月13日
    浏览(40)
  • React源码解析18(7)------ 实现事件机制(onClick事件)

    在上一篇中,我们实现了useState的hook,但由于没有实现事件机制,所以我们只能将setState挂载在window上。 而这一篇主要就是来实现事件系统,从而实现通过点击事件进行setState。 而在React中,虽然我们是将事件绑定在JSX上的某个元素上,但是其实最终的执行者是最外层的容器

    2024年02月12日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包