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

文章目录

    • 通过源码明晰的几个问题
    • Spring 中的事件
    • Springboot 是怎么做到事件监听的
      • 另外两种注册的Listener
    • 源码解析
      • 加载listener
      • SpringApplicationRunListener
      • EventPublishingRunListener
      • SimpleApplicationEventMulticaster
      • 判断 listener 是否可以接收事件
      • Java 泛型获取
    • 整体流程回顾
      • Springboot事件分发流程
      • SimpleApplicationEventMulticaster 事件分发流程
    • 问题解答
      • 1. Springboot 注册事件监听器有几种方式,分别是什么?
      • 2. 什么情况下注册的事件监听会失效(接收不到Springboot事件)?
      • 3. Springboot 利用的哪一个类做的事件分发操作?
      • 4. Spring 是如何利用泛型做到的事件分发?
      • 5. 怎么自定义 listener 达到监听多个类型事件?
      • 6. 除了Spring或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上色,还挺好玩的,附一张最爱的云堇图片。
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/32741.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Verilog基础之十一、移位寄存器实现

目录 一、前言 二、工程设计 ​2.1 工程代码 2.2 综合结果 2.3 仿真结果 一、前言 移位寄存器SRL在工程中属于使用频率较高个模块&#xff0c;可用于存储数据&#xff0c;实现串并转换&#xff1b;根据数据移动方向可分为左移寄存器&#xff0c;右移寄存器&#xff0c;左移…

测试岗入职第一天,老员工给我丢了这个文档!炒鸡好用!

不管新老测试员工应该都会对自己的测试工作进行一些总结&#xff0c;这个测试方法总结帮助了很多人&#xff0c;今天我再芬享一次&#xff0c;文末自取吧。 真的炒鸡好用&#xff01;这份笔记可以说陪我度过了测试试用期&#xff0c;里面包括90&#xff05;测试会遇到的问题&a…

ModaHub魔搭社区:向量数据库Milvus使用 MySQL 管理元数据教程

目录 使用 MySQL 管理元数据 常见问题 数据管理相关博客 使用 MySQL 管理元数据 Milvus 默认使用 SQLite 作为元数据后台管理服务&#xff0c;SQLite 内嵌于 Milvus 进程中&#xff0c;无需启动额外服务。但是在生产环境中&#xff0c;基于可靠性的考虑&#xff0c;我们强烈…

【Android Framework系列】第3章 Zygote进程相关

1 Zygote简介 Zygote是Android中最重要的一个进程&#xff0c;Zygote进程和Init进程、SystemServer进程是Android最重要的三大进程。Zygote是Android系统创建新进程的核心进程&#xff0c;负责启动Dalvik虚拟机&#xff0c;加载一些必要的系统资源和系统类&#xff0c;启动sys…

C#串口通信从入门到精通(26)——多个串口多个线程发送数据和接收数据

前言 我们在开发串口程序的过程中有时候会遇到多个串口,并且多个串口也需要在多个线程进行操作,本文就来讲解如何实现多个串口在多线程下的安全发送与接收。 1、操作界面与测试过程 我们首先使用虚拟串口助手虚拟COM1、COM2这一对串口;COM3、COM4这一对串口,然后使用代码…

Redis 原理

Redis 原理 动态字符串SDS Redis中保存的key时字符串&#xff0c;value往往是字符串或字符串集合&#xff0c;字符串是Redis中常见的数据结构 Redis没有直接使用C语言中的字符串&#xff0c;因为C语言字符串存在很多问题&#xff0c;使用起来不方便 Redis构建了一种新型的字…

TypeScript 【类型推断】与【类型别名】的使用解读

什么是类型推断&#xff1f; 在 TypeScript 中&#xff0c; 如果声明变量时&#xff0c;没有明确的指定类型&#xff0c;那么 TypeScript 会依照类型推论&#xff08;Type Inference&#xff09;的规则推断出一个类型。 以下代码虽然没有明确指定类型&#xff0c;但是会在编译的…

应急响应:系统入侵排查指南

目录 系统基本信息排查 Windows系统排查 Linux系统排查 CPU信息 操作系统信息 载入模块排查 用户排查 Windows系统用户排查 排查所有账户 Linux用户排查 root账户排查 查看所有可登录账户 查看用户错误的登录信息 查看所有用户最后登录信息 排查空口令账户 启…

【Flutter】如何在 Flutter 中获取设备 ID

文章目录 一、 前言二、 设备 ID 的重要性1. 什么是设备 ID2. 设备 ID 的作用 三、 在 Flutter 中获取设备 ID1. 需要的工具和库2. 简单代码示例3. 完整可以运行的代码 四、 注意事项1. 权限问题2. 设备兼容性问题 五、 总结 一、 前言 在移动应用开发中&#xff0c;有时我们需…

面试官: 请你讲下AMS在Android起到什么作用……

心理分析&#xff1a;这道题在发生在大多数场景下。面对这道题 很多求职很茫然&#xff0c;不知道该如何说起。AMS本身比较复杂难以理解。工作多年也很难弄清AMS的作用&#xff0c;其实我们大可从以下几点入手组件启动、进程切换、Crash异常入手 求职者:AMS难以表述 我们就从最…

【机器学习】PCA案例的python实现

一、说明 虽然可以通过更改优化算法来加快机器学习算法的拟合速度&#xff0c;但加快算法速度的更常用方法是使用主成分分析 &#xff08;PCA&#xff09;。如果您的学习算法由于输入维度太高而太慢&#xff0c;那么使用 PCA 加速它可能是一个合理的选择。这可能是PCA最常见的应…

正则表达式-捕获组,命名捕获组,非捕获组

正则表达式的作用 测试目标字符串是否符合规则 返回true/false按照规则从目标字符串提取内容 返回匹配的数组 在线测试工具 regex101: build, test, and debug regexRegular expression tester with syntax highlighting, explanation, cheat sheet for PHP/PCRE, Python, …

【单片机】STM32F103C8T6 最小系统板原理图

STM32F103C8T6是一款基于ARM Cortex-M3内核的32位微控制器&#xff0c;由STMicroelectronics&#xff08;ST&#xff09;公司生产。它是STMicroelectronics的STM32系列微控制器中的一员&#xff0c;被广泛应用于嵌入式系统和电子设备中。 STM32F103C8T6单片机的主要特点和资源…

基于.Net6使用YoloV8的分割模型

前言 在目标检测一文中&#xff0c;我们学习了如何处理Onnx模型&#xff0c;并的到目标检测结果&#xff0c;在此基础上&#xff0c;本文实现基于.Net平台的实例分割任务。 执行YoloV8的分割任务后可以得到分割.pt模型。由于Python基本不用于工业软件的部署&#xff0c;最终还…

SpringBoot 如何使用 @RequestBody 进行数据校验

SpringBoot 如何使用 RequestBody 进行数据校验 在 Web 开发中&#xff0c;前台向后台发送数据是非常常见的场景。而在 SpringBoot 框架中&#xff0c;我们通常使用 RequestBody 注解来接收前台发送的 JSON 数据&#xff0c;并将其转化为 Java 对象。但是&#xff0c;接收到的…

Zookeeper集群的特点

一、Zookeeper集群的特点 Zookeeper:一个领导者 (Leader)&#xff0c;多个跟随者 (Follower) 组成的集群集群中只要有半数以上节点存活&#xff0c;Zookeeper集群就能正常服务。所以Zookeeper适合安装奇数台服务器全局数据一致:每个Server保存一份相同的数据副本&#xff0c;C…

git——使用ssh连接远程仓库

文章目录 前言一. 获取邮箱和密码1. 本地配置你的名字和邮箱2. 使用命令获取你本地的邮箱和密码 二、生成ssh公钥1.任意一个文件夹路径打开Git Bash Here并输入以下命令连按三次回车2. 根据上面红框部分的地址打开文件夹3. 打开并查看id_rsa.pub 文件 三、在GitHub上连接ssh1. …

UE5.1.1 C++从0开始(15.作业4个人作业分享)

教程链接&#xff1a;https://www.bilibili.com/video/BV1nU4y1X7iQ 好吧这个作业应该是之前写的&#xff0c;但是我发现我没写&#xff0c;后面我又回去自己写了一遍再看代码&#xff0c;感觉上大差不差&#xff0c;各位可以看着我的和老师的还有自己的对比下。 SBTService_…

[LeetCode周赛复盘] 第 107 场双周赛20230624

[LeetCode周赛复盘] 第 107 场双周赛20230624 一、本周周赛总结6898. 字符串连接删减字母1. 题目描述2. 思路分析3. 代码实现 6895. 构造最长的新字符串1. 题目描述2. 思路分析3. 代码实现 6898. 字符串连接删减字母1. 题目描述2. 思路分析3. 代码实现 6468. 统计没有收到请求…

Consul 理解

Consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案&#xff0c;不再需要依赖其他工具&#xff08;比如ZooKeeper等&#xff09;。服务部署简单&#xff0c;只有一…