https://www.bilibili.com/video/BV12C4y1s7dR?p=11

监听器模式要素

  • 事件
  • 监听器
  • 广播器
  • 触发机制

Springboot中监听模式总结

  1. 在SpringApplication初始化中从META_INF/spring.factories获取Listeners
  2. 创建监听器
  3. 创建广播器
  4. 将监听器在广播器中进行注册
  5. 事件触发时,监听器监听到,同时将创建的事件传给广播器,执行广播器,通过回调调用实际事件中方法

Springboot中监听器各要素

事件
  1. public abstract class ApplicationEvent extends EventObject {
  2. /** use serialVersionUID from Spring 1.2 for interoperability. */
  3. private static final long serialVersionUID = 7099057708183571937L;
  4. /** System time when the event happened. */
  5. private final long timestamp;
  6. /**
  7. * Create a new {@code ApplicationEvent}.
  8. * @param source the object on which the event initially occurred or with
  9. * which the event is associated (never {@code null})
  10. */
  11. public ApplicationEvent(Object source) {
  12. super(source);
  13. this.timestamp = System.currentTimeMillis();
  14. }
  15. /**
  16. * Return the system time in milliseconds when the event occurred.
  17. */
  18. public final long getTimestamp() {
  19. return this.timestamp;
  20. }
  21. }
监听器
  1. @FunctionalInterface
  2. public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
  3. /**
  4. * Handle an application event.
  5. * @param event the event to respond to
  6. */
  7. void onApplicationEvent(E event);
  8. }
广播器
  1. public interface ApplicationEventMulticaster {
  2. void addApplicationListener(ApplicationListener<?> listener);
  3. void addApplicationListenerBean(String listenerBeanName);
  4. void removeApplicationListener(ApplicationListener<?> listener);
  5. void removeApplicationListenerBean(String listenerBeanName);
  6. void removeAllListeners();
  7. void multicastEvent(ApplicationEvent event);
  8. void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
  9. }
事件触发,如:

SpringAppliation中listeners.starting();

源码梳理

在SpringApplication初始化中setListeners

在SpringApplication构造函数中,执行setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

  1. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  2. this.resourceLoader = resourceLoader;
  3. Assert.notNull(primarySources, "PrimarySources must not be null");
  4. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  5. this.webApplicationType = WebApplicationType.deduceFromClasspath();
  6. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  7. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  8. this.mainApplicationClass = deduceMainApplicationClass();
  9. }

执行SpringFactoriesLoader类中loadSpringFactories方法,将jar包中所有META_INF/spring.factories文件中的属性进行加载(在setInitializers中已经加载,所以在setListeners直接从MultiValueMap缓存中取)

  1. Enumeration<URL> urls = (classLoader != null ?
  2. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  3. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

调用链:

SpringApplication#init() --> SpringApplication#getSpringFactoriesInstances() --> SpringFactoriesLoader#loadFactoryNames() --> SpringFactoriesLoader#loadSpringFactories()

loadSpringFactories代码:

  1. private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
  2. MultiValueMap<String, String> result = cache.get(classLoader);
  3. if (result != null) {
  4. return result;
  5. }
  6. try {
  7. Enumeration<URL> urls = (classLoader != null ?
  8. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  9. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  10. result = new LinkedMultiValueMap<>();
  11. while (urls.hasMoreElements()) {
  12. URL url = urls.nextElement();
  13. UrlResource resource = new UrlResource(url);
  14. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  15. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  16. String factoryTypeName = ((String) entry.getKey()).trim();
  17. for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
  18. result.add(factoryTypeName, factoryImplementationName.trim());
  19. }
  20. }
  21. }
  22. cache.put(classLoader, result);
  23. return result;
  24. }
  25. catch (IOException ex) {
  26. throw new IllegalArgumentException("Unable to load factories from location [" +
  27. FACTORIES_RESOURCE_LOCATION + "]", ex);
  28. }
  29. }

调用createSpringFactoriesInstances方法进行实例化

  1. private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
  2. ClassLoader classLoader, Object[] args, Set<String> names) {
  3. List<T> instances = new ArrayList<>(names.size());
  4. for (String name : names) {
  5. try {
  6. Class<?> instanceClass = ClassUtils.forName(name, classLoader);
  7. Assert.isAssignable(type, instanceClass);
  8. Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
  9. T instance = (T) BeanUtils.instantiateClass(constructor, args);
  10. instances.add(instance);
  11. }
  12. catch (Throwable ex) {
  13. throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
  14. }
  15. }
  16. return instances;
  17. }

将获取到的listeners注册到List<ApplicationListener<?>> listeners中

  1. public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
  2. this.listeners = new ArrayList<>(listeners);
  3. }
获取SpringApplicationRunListener
  1. SpringApplicationRunListeners listeners = getRunListeners(args);
  1. private SpringApplicationRunListeners getRunListeners(String[] args) {
  2. Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  3. return new SpringApplicationRunListeners(logger,
  4. getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
  5. }

key为org.springframework.boot.SpringApplicationRunListener,从之前缓存的map中取出SpringApplicationRunListener,value为EventPublishingRunListener

对EventPublishingRunListener进行初始化,创建SimpleApplicationEventMulticaster广播器

  1. public EventPublishingRunListener(SpringApplication application, String[] args) {
  2. this.application = application;
  3. this.args = args;
  4. this.initialMulticaster = new SimpleApplicationEventMulticaster();
  5. for (ApplicationListener<?> listener : application.getListeners()) {
  6. this.initialMulticaster.addApplicationListener(listener);
  7. }
  8. }

将监听器注册到广播器中:

  1. for (ApplicationListener<?> listener : application.getListeners()) {
  2. this.initialMulticaster.addApplicationListener(listener);
  3. }
事件触发

SpringApplication中listeners.starting();

执行到EventPublishingRunListener中

  1. @Override
  2. public void starting() {
  3. this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
  4. }

进行广播事件:

  1. @Override
  2. public void multicastEvent(ApplicationEvent event) {
  3. multicastEvent(event, resolveDefaultEventType(event));
  4. }
  5. @Override
  6. public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
  7. ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
  8. Executor executor = getTaskExecutor();
  9. for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
  10. if (executor != null) {
  11. executor.execute(() -> invokeListener(listener, event));
  12. }
  13. else {
  14. invokeListener(listener, event);
  15. }
  16. }
  17. }

然后去调用具体事件的onApplicationEvent方法

Spring源码之Springboot中监听器介绍的更多相关文章

  1. Spring源码之AbstractApplicationContext中refresh方法注释

    https://blog.csdn.net/three_stand/article/details/80680004 refresh() prepareRefresh(beanFactory) 容器状 ...

  2. Spring源码系列(一)--详解介绍bean组件

    简介 spring-bean 组件是 IoC 的核心,我们可以通过BeanFactory来获取所需的对象,对象的实例化.属性装配和初始化都可以交给 spring 来管理. 针对 spring-bean ...

  3. Mybatis源码解读-SpringBoot中配置加载和Mapper的生成

    本文mybatis-spring-boot探讨在springboot工程中mybatis相关对象的注册与加载. 建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此 ...

  4. Spring源码解析-Advice中的Adapter模式

    在spring中与通知相关的类有: 以Advice结尾的通知接口    MethodBeforeAdvice    AfterReturningAdvice   ThrowsAdvice 以Inter ...

  5. spring源码学习之路---AOP初探(六)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...

  6. spring源码学习之bean的加载(一)

    对XML文件的解析基本上已经大致的走了一遍,虽然没有能吸收多少,但是脑子中总是有些印象的,接下来看下spring中的bean的加载,这个比xml解析复杂的多.这个加载,在我们使用的时候基本上是:Bea ...

  7. spring源码学习(一)--AOP初探

    LZ以前一直觉得,学习spring源码,起码要把人家的代码整体上通读一遍,现在想想这是很愚蠢的,spring作为一个应用平台,不是那么好研究透彻的,而且也不太可能有人把spring的源码全部清楚的过上 ...

  8. Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?

    阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...

  9. Spring源码分析(十四)从bean的实例中获取对象

    摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 在getBean方法中,getObjectForBeanlnstance ...

随机推荐

  1. java流程控制之习题

     经过近段时间的学习,差不多也掌握了java的流程控制以及基本知识,下面就来一起练练习题吧,看能做出来几道. 第一道题:假设小明有100块钱,这时候小明去超市需要换零钱,超市提供的零钱有1元面值,2元 ...

  2. ansible用authorized_key模块批量推送密钥到受控主机(免密登录)(ansible2.9.5)

    一,ansible的authorized_key模块的用途 用来配置密钥实现免密登录: ansible所在的主控机生成密钥后,如何把公钥上传到受控端? 当然可以用ssh-copy-id命令逐台手动处理 ...

  3. C# Hash算法

    #region Hash算法 /// <summary> /// Hash算法 /// </summary> /// <param name="myStr&qu ...

  4. 一个鲜为人知但很实用的Windows使用技巧

    作为一个电脑党,最无法忍受的就是每次开机都要手动打开那些必要的程序.有没办法让这些程序自动打开呢?今天小编意外地得到了一个方法,现在分享给大家. (以腾讯桌面整理为例) 1,Win + R 2,输入t ...

  5. Linux基础命令列表

    命令列表 A alias apt apt-get arp -n -s arping ab B bc basename bash -n -x bzip2 bunzip2 bzcat blkid brct ...

  6. MySQL 日志之 binlog 格式 → 关于 MySQL 默认隔离级别的探讨

    开心一刻 产品还没测试直接投入生产时,这尼玛... 背景问题 在讲 binlog 之前,我们先来回顾下主流关系型数据库的默认隔离级别,是默认隔离级别,不是事务有哪几种隔离级别,别会错题意了 1.Ora ...

  7. vue中跳转页面逻辑

    跳转详情页面具体代码 写这个页面需要安装两个 1.安装axios命令 Cnpm install axios --save 2.安装vant Cnpm install vant --save 在inde ...

  8. asp.net core的授权过滤器中获取action上的Attribute

    var action = context.ActionDescriptor as ControllerActionDescriptor; var permission = action.MethodI ...

  9. 020_Typora使用

    目录 Typora使用 下载 安装 设置 使用 Typora使用 Typora插入本地图片为本地路径,网络上无法查看,所以建议只插入网络图片. 下载 百度搜索官网 下载 安装 设置 视图->显示 ...

  10. 两个有序数列找第k小

    给定一个数组,数组中的数据无序,在一个数组中找出其第k个最小的数,例如对于数组x,x = {3,2,1,4,5,6},则其第2个最小的数为2  两个有序数组 找第k小 * 方案一 合并遍历 * 二:游 ...