Spring源码分析之事件机制——观察者模式(二)

目录

获取监听器的入口方法

实际检索监听器的核心方法

监听器类型检查方法

监听器的注册过程

监听器的存储结构

过程总结


Spring源码分析之事件机制------观察者模式(一)-CSDN博客

Spring源码分析之事件机制------观察者模式(二)-CSDN博客

Spring源码分析之事件机制------观察者模式(三)-CSDN博客

这两篇文章是这个篇章的前篇和后篇,感兴趣的读者可以阅读一下,从spring源码分析观察者模式

获取监听器的入口方法

java 复制代码
public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster {
    // 存储所有监听器的集合
    private final DefaultListenerRetrieverdefaultRetriever = new DefaultListenerRetriever();

    // 缓存事件类型与监听器的映射关系
    private final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = 
        new ConcurrentHashMap<>(64);

   protected Collection<ApplicationListener<?>> getApplicationListeners(
        ApplicationEvent event, ResolvableType eventType) {
    // 获取事件源和源类型
    Object source = event.getSource();
    Class<?> sourceType = source != null ? source.getClass() : null;
    
    // 创建缓存key
    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
    
    // 准备新的缓存检索器
    CachedListenerRetriever newRetriever = null;
    // 尝试从缓存中获取已存在的检索器
    CachedListenerRetriever existingRetriever = 
        (CachedListenerRetriever)this.retrieverCache.get(cacheKey);
    
    // 如果缓存中不存在,且类加载器安全(防止类加载器泄漏),则创建新的检索器
    if (existingRetriever == null && 
        (this.beanClassLoader == null || 
         ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && 
         (sourceType == null || 
          ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
        
        newRetriever = new CachedListenerRetriever();
        // 使用CAS操作将新检索器放入缓存
        existingRetriever = (CachedListenerRetriever)this.retrieverCache
            .putIfAbsent(cacheKey, newRetriever);
        if (existingRetriever != null) {
            newRetriever = null;
        }
    }

    // 如果存在缓存的检索器,尝试获取缓存的监听器
    if (existingRetriever != null) {
        Collection<ApplicationListener<?>> result = 
            existingRetriever.getApplicationListeners();
        if (result != null) {
            return result;
        }
    }

    // 如果缓存未命中,检索匹配的监听器
    return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}

这个方法实现了监听器检索的缓存机制,通过缓存来提高性能,同时考虑了类加载器安全性。

实际检索监听器的核心方法

java 复制代码
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, 
        @Nullable Class<?> sourceType, 
        @Nullable CachedListenerRetriever retriever) {
        
    // 存储所有匹配的监听器
    List<ApplicationListener<?>> allListeners = new ArrayList();
    // 如果有缓存检索器,创建过滤后的监听器集合
    Set<ApplicationListener<?>> filteredListeners = 
        retriever != null ? new LinkedHashSet() : null;
    Set<String> filteredListenerBeans = 
        retriever != null ? new LinkedHashSet() : null;
    
    // 同步获取已注册的监听器和监听器Bean名称
    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
    synchronized(this.defaultRetriever) {
        listeners = new LinkedHashSet(this.defaultRetriever.applicationListeners);
        listenerBeans = new LinkedHashSet(this.defaultRetriever.applicationListenerBeans);
    }

    // 处理已实例化的监听器
    for(ApplicationListener<?> listener : listeners) {
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
                filteredListeners.add(listener);
            }
            allListeners.add(listener);
        }
    }

    // 处理监听器Bean
    if (!listenerBeans.isEmpty()) {
        ConfigurableBeanFactory beanFactory = getBeanFactory();
        for(String listenerBeanName : listenerBeans) {
            try {
                // 检查Bean是否支持该事件
                if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
                    ApplicationListener<?> listener = beanFactory.getBean(
                        listenerBeanName, ApplicationListener.class);
                    // 避免重复添加
                    if (!allListeners.contains(listener) && 
                        supportsEvent(listener, eventType, sourceType)) {
                        // 根据Bean的作用域决定是否缓存
                        if (retriever != null) {
                            if (beanFactory.isSingleton(listenerBeanName)) {
                                filteredListeners.add(listener);
                            } else {
                                filteredListenerBeans.add(listenerBeanName);
                            }
                        }
                        allListeners.add(listener);
                    }
                } else {
                    // 移除不支持的监听器
                    Object listener = beanFactory.getSingleton(listenerBeanName);
                    if (retriever != null) {
                        filteredListeners.remove(listener);
                    }
                    allListeners.remove(listener);
                }
            } catch (NoSuchBeanDefinitionException ex) {
                // 忽略不存在的Bean
            }
        }
    }

    // 按照@Order注解排序
    AnnotationAwareOrderComparator.sort(allListeners);
    
    // 更新缓存
    if (retriever != null) {
        if (filteredListenerBeans.isEmpty()) {
            retriever.applicationListeners = new LinkedHashSet(allListeners);
            retriever.applicationListenerBeans = filteredListenerBeans;
        } else {
            retriever.applicationListeners = filteredListeners;
            retriever.applicationListenerBeans = filteredListenerBeans;
        }
    }

    return allListeners;
}

这个方法是实际检索监听器的核心实现,它处理了已实例化的监听器和尚未实例化的监听器Bean,同时考虑了Bean的作用域和缓存策略。

监听器类型检查方法

java 复制代码
private boolean supportsEvent(
        ConfigurableBeanFactory beanFactory, 
        String listenerBeanName, 
        ResolvableType eventType) {
        
    // 获取监听器的类型
    Class<?> listenerType = beanFactory.getType(listenerBeanName);
    
    // 如果是特殊的监听器类型,直接返回true
    if (listenerType != null && 
        !GenericApplicationListener.class.isAssignableFrom(listenerType) && 
        !SmartApplicationListener.class.isAssignableFrom(listenerType)) {
        
        // 检查是否支持事件类型
        if (!supportsEvent(listenerType, eventType)) {
            return false;
        }
        
        try {
            // 获取Bean定义并检查泛型类型
            BeanDefinition bd = beanFactory.getMergedBeanDefinition(listenerBeanName);
            ResolvableType genericEventType = bd.getResolvableType()
                .as(ApplicationListener.class)
                .getGeneric(new int[0]);
            return genericEventType == ResolvableType.NONE || 
                   genericEventType.isAssignableFrom(eventType);
        } catch (NoSuchBeanDefinitionException ex) {
            return true;
        }
    }
    return true;
}

这个方法负责检查监听器是否支持特定的事件类型,它考虑了泛型类型和特殊的监听器接口。整个实现展示了Spring如何高效地管理和匹配事件监听器,通过缓存机制提高性能,同时保证类型安全和正确的监听器顺序。这种实现既保证了功能的完整性,又确保了运行时的效率。

监听器的注册过程

java 复制代码
public abstract class AbstractApplicationContext {
    
    // 在容器刷新过程中注册监听器
    protected void registerListeners() {
        // 首先注册静态指定的监听器
        for (ApplicationListener<?> listener : getApplicationListeners()) {
            getApplicationEventMulticaster().addApplicationListener(listener);
        }

        // 获取配置的监听器Bean名称
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
            // 将监听器Bean名称添加到多播器
            getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }

        // 发布早期事件
        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
        this.earlyApplicationEvents = null;
        if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
            for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
                getApplicationEventMulticaster().multicastEvent(earlyEvent);
            }
        }
    }
}

这段代码展示了Spring如何在容器启动时注册监听器。对于像UserCacheListener这样的组件,它们会被Spring容器扫描并注册到ApplicationEventMulticaster中。

监听器的存储结构

java 复制代码
 private class CachedListenerRetriever {
        @Nullable
        public volatile Set<ApplicationListener<?>> applicationListeners;
        @Nullable
        public volatile Set<String> applicationListenerBeans;

        private CachedListenerRetriever() {
        }

        @Nullable
        public Collection<ApplicationListener<?>> getApplicationListeners() {
            Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;
            Set<String> applicationListenerBeans = this.applicationListenerBeans;
            if (applicationListeners != null && applicationListenerBeans != null) {
                List<ApplicationListener<?>> allListeners = new ArrayList(applicationListeners.size() + applicationListenerBeans.size());
                allListeners.addAll(applicationListeners);
                if (!applicationListenerBeans.isEmpty()) {
                    BeanFactory beanFactory = AbstractApplicationEventMulticaster.this.getBeanFactory();

                    for(String listenerBeanName : applicationListenerBeans) {
                        try {
                            allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class));
                        } catch (NoSuchBeanDefinitionException var8) {
                        }
                    }
                }

                if (!applicationListenerBeans.isEmpty()) {
                    AnnotationAwareOrderComparator.sort(allListeners);
                }

                return allListeners;
            } else {
                return null;
            }
        }
    }

CachedListenerRetriever 其实是AbstractApplicationEventMulticaster 的静态内部类

过程总结

Spring扫描到@Component注解,创建BeanDefinition

Spring容器启动时实例化Bean

在AbstractApplicationContext.registerListeners()中注册

相关推荐
DC_BLOG1 分钟前
数据结构排序
java·数据结构·算法·排序算法
DevOpsDojo2 分钟前
Bash语言的函数实现
开发语言·后端·golang
Upuping3 分钟前
Servlet详解
java·后端·web
Lang_xi_4 分钟前
bash相关习题复习
开发语言·bash
DevOpsDojo5 分钟前
Bash语言的软件工程
开发语言·后端·golang
摇光~6 分钟前
【shell编程】报错信息:bash: bad file descriptor(包含6种解决方法)
开发语言·ssh·bug·bash·shell
华年源码11 分钟前
基于springboot的房屋租赁系统(源码+数据库+文档)
java·数据库·spring boot·后端·毕业设计·源码·springboot
Jason-河山15 分钟前
如何利用Java爬虫批量获取商品信息
java·开发语言·爬虫
シ風箏19 分钟前
Kafka【应用 04】Java实现筛选查询Kafka符合条件的最新数据(保证数据最新+修改map对象key的方法+获取指定数量的记录)源码分享粘贴可用
java·kafka·linq
CodeChampion19 分钟前
68.基于SpringBoot + Vue实现的前后端分离-心灵治愈交流平台系统(项目 + 论文PPT)
java·vue.js·spring boot·mysql·elementui·node.js·idea