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

news2024/10/7 16:15:22

文章目录

    • 通过源码明晰的几个问题
    • 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上色,还挺好玩的,附一张最爱的云堇图片。
请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【前端|HTML系列第1篇】HTML的基础介绍与初次尝试

大家好&#xff0c;欢迎来到前端入门系列的第一篇博客。在这个系列中&#xff0c;我们将一起学习前端开发的基础知识&#xff0c;从零开始构建网页和Web应用程序。本篇博客将为大家介绍HTML&#xff08;超文本标记语言&#xff09;的基础概念和标签&#xff0c;帮助你快速入门。…

Git进阶系列 | 6. 交互式Rebase

Git是最流行的代码版本控制系统&#xff0c;这一系列文章介绍了一些Git的高阶使用方式&#xff0c;从而帮助我们可以更好的利用Git的能力。本系列一共8篇文章&#xff0c;这是第6篇。原文&#xff1a;Interactive Rebase: Clean up your Commit History[1] 交互式Rebase是Git命…

数据结构:二叉树经典例题(单选题)-->你真的掌握二叉树了吗?(第二弹)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关二叉树的经典例题&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…

百度开源,一款强大的压测工具,可模拟几十亿并发场景

优点 性能强大统计信息详细使用场景丰富性能 HTTP 每秒新建连接数HTTP 吞吐HTTP 并发连接数UDP TX PPS测试环境配置统计数据开始使用 设置大页编译 DPDK编译 dperf绑定网卡启动 dperf server从客户端发送请求运行测试开源地址 dperf 是一款基于 DPDK 的 100Gbps 网络性能和负载…

小白快速自建博客--halo博客

小白快速自建博客–halo博客 前言&#xff1a;如何快速地搭建属于个人的博客&#xff0c;以下就推荐一个可以个性化的定制属于个人的专属博客–halo博客。 Docker安装 关于docker安装&#xff0c;可以查看文章&#xff1a;Linux下安装docker Docker相关指令 docker相关指令…

网络套接字基本概念

文章目录 1. 认识IP地址2. 认识端口号2.1 理解 "端口号" 和 "进程ID"2.2 理解源端口号和目的端口号 3. 认识TCP协议和UDP协议4. 网络字节序5. sockaddr结构 1. 认识IP地址 IP协议有两个版本&#xff0c;IPv4和IPv6。没有特殊说明的&#xff0c;默认都是指…

第五章 作业(149A)【计算机系统结构】

第五章 作业【计算机系统结构】 前言推荐第五章 作业148 补充910 最后 前言 2023-6-24 10:43:46 以下内容源自《【计算机系统结构】》 仅供学习交流使用 推荐 第三章 作业&#xff08;7BF&#xff09;【计算机系统结构】 答案参考&#xff1a; https://www.docin.com/p-8…

HTML 教程:学习如何构建网页||HTML 简介

HTML 简介 HTML 简介 现在您可以通过如下的一个 HTML 实例来建立一个简单的 HTML 页面&#xff0c;以此来简单了解一下 HTML 的结构。 HTML 实例 <!DOCTYPE html> <html> <head> <title>页面标题(w3cschool.cn)</title> </head> <…

中国电子学会2023年05月份青少年软件编程C++等级考试试卷四级真题(含答案)

1.怪盗基德的滑翔翼 怪盗基德是一个充满传奇色彩的怪盗&#xff0c;专门以珠宝为目标的超级盗窃犯。而他最为突出的地方&#xff0c;就是他每次都能逃脱中村警部的重重围堵&#xff0c;而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。 有一天&#xff0c;怪盗基德像…

混频器【Multisim】【高频电子线路】

目录 一、实验目的与要求 二、实验仪器 三、实验内容与测试结果 1、测试输入输出波形&#xff0c;说明两者之间的关系 2、测试输出信号的频谱(傅里叶分析法) 3、将其中一个二极管反接&#xff0c;测试输出波形&#xff0c;并解释原因&#xff1b;将两个二极管全部反接&am…

PID相关参数讲解:1、比例系数Kp与静态误差

PID的结构与公式 来研究静态误差的同学&#xff0c;应该是对PID的原理有一定理解了&#xff0c;简单的概念也不用过多重复。 比例控制时PID控制中最简单的一个&#xff0c;很多能用代码编写PID代码的同学&#xff0c;也不一定理解这个比例系数Kp的意义&#xff0c;以及比例控制…

C++进阶—多态

目录 0. 前言 1. 多态的概念 1.1 概念 2. 多态的定义及实现 2.1多态的构成条件 2.2 虚函数 2.3虚函数的重写 2.4 C11 override 和 final 3. 抽象类 3.1 概念 3.2 接口继承和实现继承 4.多态的原理 4.1虚函数表 4.2 变态选择题分析多态调用 4.3 多态的原理 4.4 动…

透过小说中的境界划分,看看你的能力处于哪个水平

文章目录 1. 写在前面2. 散修3. 练气期4. 筑基期5. 结丹期6. 元婴期7. 化神期8. 练虚期 1. 写在前面 当我们某天开始走出校园&#xff0c;踏入社会的时候&#xff0c;是否也会感到一丝恐惧与焦虑&#xff1f;当我们各自奔走加入到江湖大大小小的门派中&#xff0c;为了企业与老…

EL标签-给JSP减负

https://blog.csdn.net/weixin_42259823/article/details/85945149 安装使用 1. 通过命令行创建maven项目 2. 安装jstl包 <dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version> </depen…

仙境传说RO:服务器外网架设登陆教程

仙境传说RO&#xff1a;服务器外网架设登陆教程 大家好我是艾西&#xff0c;今天跟大家说一下自己编译的仙境传说RO服务端怎么开启外网让小伙伴和你一起玩。 目前开放的现成端有以下版本&#xff1a; 仙境传说ro守护永恒的爱 仙境传说ro爱如初见 仙境传说ro黑色派对 仙境…

docker-compose把微服务部署到centos7

前言 这里主要记录以下微服务使用docker、docker-compose部署遇到的一些问题&#xff0c;大佬可以绕道去看看自动化集成这篇文章 部署之前你需要准备一些内容 微服务 这里feign-api是用来做服务之间相互调用的&#xff0c;单独抽离成了一个模块&#xff0c;gateway是服务网关&…

中国电子学会2023年05月份青少年软件编程C++等级考试试卷三级真题(含答案)

1.找和为K的两个元素 在一个长度为n(n < 1000)的整数序列中&#xff0c;判断是否存在某两个元素之和为k。 【输入】 第一行输入序列的长度n和k&#xff0c;用空格分开。 第二行输入序列中的n个整数&#xff0c;用空格分开。 【输出】 如果存在某两个元素的和为k&#xff0c…

CentOs中文件权限命令

文件权限&#xff1a; ls -l命令查看文件详情&#xff0c;前十位就是文件的类型和权限 第一位&#xff1a;类型&#xff1a; - 普通文件 d 目录 l 链接文件&#xff08;快捷方式&#xff09;link 2~4位&#xff1a;所有者的权限 5~7位&#xff1a;所有者所在组其它用户的权限 …

AI绘图-StableDiffusion安装

AI绘图-StableDiffusion安装 安装Python 1、去官网 https://www.python.org/downloads/ 下载Python3.10.10版本&#xff0c;网上建议下载3.10.6版本。 2、安装Python,打开安装界面&#xff0c;注意把Add Python to PATH选项勾上&#xff0c;然后选择自定义安装 3、点击WinR…

【数据结构与算法】2、链表(简单模拟 Java 中的 LinkedList 集合,反转链表面试题)

目录 一、链表基本概念和基本代码实现二、链表、动态数组整合&#xff08;面向接口编程&#xff09;三、clear()四、add(int index, E element)(1) 找到 index 位置的节点(2) get(int index) 和 set(int index, E element)(3) add(int index, E element) 五、remove(int index)…