Spring 监听机制

在看一些开源代码的时候,经常出现Spring 发布事件的身影。

JDK其实就有监听模式

/**
 * @Author lixw
 * @Date 5/22/20 8:59 AM
 */
public class Producer extends Observable {
    List<Observer> listener = new ArrayList<>();
    public Producer() {
        //注册监听者
        listener.add(new Consumer());
    }

    //发布更新 --- 广播消费给注册到Producer的消费者
    public void  publishEvent(){
        System.out.println("生产者有更新,通知消费者");
        Producer producer = this;
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        for (Observer observer : listener) {
            observer.update(producer, LocalDateTime.now().format(formatter) + "发送新的消息");
        }
    }

    public static void main(String[] args) {
        new Producer().publishEvent();
    }

    //定义消费者 也就是监听者
    class Consumer implements Observer {
        //定义接收到消息的逻辑
        @Override
        public void update(Observable o, Object arg) {
            System.out.println("消费者接受到" + o + "的消息:" + arg);
        }
    }
}
  1. 事件的发布者继承Observable
  2. 事件的监听者实现Observer,
  3. 定义监听列表,讲监听者注册到发布者列表中
  4. 发布事件的时候,通过监听者列表进行广播
  5. 监听者在update中实现监听逻辑

可以看到第三步是建立事件发布者和监听者联系的核心

那么Spring的具体实现呢?结合Spring Boot的自动配置文件加载机制来看看

image.png

事件发布者: ApplicationEnvironmentPreparedEvent

类似 JDK的Observable

监听者: ConfigFileApplicationListener

类似 JDK的Observer

查看ApplicationEnvironmentPreparedEvent的源码并没有发现的实现有地方可以注册监听者或者存储监听者
那么两者是如何建立联系的?通过Debug的栈帧,可以看到


image.png

line134: 求出了event(ApplicationEnvironmentPreparedEvent)的所有的监听者,line139通过Debug信息可以看到listener就是ConfigFileApplicationListener

继续看AbstractApplicationEventMulticaster.getApplicationListeners源码

    /**
     * Return a Collection of ApplicationListeners matching the given
     * event type. Non-matching listeners get excluded early.
     * @param event the event to be propagated. Allows for excluding
     * non-matching listeners early, based on cached matching information.
     * @param eventType the event type
     * @return a Collection of ApplicationListeners
     * @see org.springframework.context.ApplicationListener
     */
    protected Collection<ApplicationListener<?>> getApplicationListeners(
            ApplicationEvent event, ResolvableType eventType) {

        Object source = event.getSource();
        Class<?> sourceType = (source != null ? source.getClass() : null);
        ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
//走缓存
        // Quick check for existing entry on ConcurrentHashMap...
        ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
        if (retriever != null) {
            return retriever.getApplicationListeners();
        }

        if (this.beanClassLoader == null ||
                (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                        (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
            // Fully synchronized building and caching of a ListenerRetriever
            synchronized (this.retrievalMutex) {
                retriever = this.retrieverCache.get(cacheKey);
                if (retriever != null) {
                    return retriever.getApplicationListeners();
                }
                retriever = new ListenerRetriever(true);
               // 求监听者列表
                Collection<ApplicationListener<?>> listeners =
                        retrieveApplicationListeners(eventType, sourceType, retriever);
                //缓存
                this.retrieverCache.put(cacheKey, retriever);
                return listeners;
            }
        }
        else {
            // No ListenerRetriever caching -> no synchronization necessary
            return retrieveApplicationListeners(eventType, sourceType, null);
        }
    }
    /**
     * Actually retrieve the application listeners for the given event and source type.
     * @param eventType the event type
     * @param sourceType the event source type
     * @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
     * @return the pre-filtered list of application listeners for the given event and source type
     */
    private Collection<ApplicationListener<?>> retrieveApplicationListeners(
            ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

        List<ApplicationListener<?>> allListeners = new ArrayList<>();
        Set<ApplicationListener<?>> listeners;
        Set<String> listenerBeans;
        synchronized (this.retrievalMutex) {
            listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
            listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
        }

        // Add programmatically registered listeners, including ones coming
        // from ApplicationListenerDetector (singleton beans and inner beans).
        for (ApplicationListener<?> listener : listeners) {
//事件和监听者建立联系
            if (supportsEvent(listener, eventType, sourceType)) {
                if (retriever != null) {
                    retriever.applicationListeners.add(listener);
                }
                allListeners.add(listener);
            }
        }
                ...
        return allListeners;
    }

supportsEvent的实现


image.png

继续看ConfigFileApplicationListener实现了SmartApplicationListener

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {}

public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
                || ApplicationPreparedEvent.class.isAssignableFrom(eventType);
    }
}

listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);

可以知道的defaultRetriever中存储了监听者列表是怎么加载的呢?

  1. 现在SpringApplication的源码(new SpringApplication()的时候执行)
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
          //加载了监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

Spring Boot 初始化的时候EventPublishingRunListener的时候会执行

    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
       //从SpringApplication拿到监听者列表
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

SimpleApplicationEventMulticaster(AbstractApplicationEventMulticaster)的代码

@Override
    public void addApplicationListener(ApplicationListener<?> listener) {
        synchronized (this.retrievalMutex) {
            // Explicitly remove target for a proxy, if registered already,
            // in order to avoid double invocations of the same listener.
            Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
            if (singletonTarget instanceof ApplicationListener) {
                this.defaultRetriever.applicationListeners.remove(singletonTarget);
            }
           //讲监听器添加到defaultRetriever中
            this.defaultRetriever.applicationListeners.add(listener);
            this.retrieverCache.clear();
        }
    }

到这,ApplicationListener是什么时候在那生成,存储在那,如何和event关联就只全部知道了。总结

  1. SpringApplication有一个ApplicationListener监听者列表,实例化的时候,Spring会ApplicationListener类型通过JDK发射机制生成,保存到SpringApplication的 listeners 列表中
  2. Spring容器初始化到EventPublishingRunListener的时候,会SpringApplication保存的ApplicationListener实例保存到SimpleApplicationEventMulticaster对象的defaultRetriever对象中
  3. SpringApplication执行run方法的时候,会生成一个SpringApplicationRunListeners对象
    ,并且在run方法中,会出发配置环境初始化的工作,并生成ApplicationEnvironmentPreparedEvent事件对象
    SpringApplicationRunListeners listeners = getRunListeners(args);
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
    --> listeners.environmentPrepared(environment);
    --> listener.environmentPrepared(environment);
    --> this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    4.SpringBoot 通过广播机制,发布事件。在SimpleApplicationEventMulticaster并通过ApplicationEvent event和ResolvableType eventType过滤筛选出监听event事件的监听者,触发监听者方法
  @Override
  public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
      ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
      Executor executor = getTaskExecutor();
      for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
          if (executor != null) {
              executor.execute(() -> invokeListener(listener, event));
          }
          else {
              invokeListener(listener, event);
          }
      }
  }

SimpleApplicationEventMulticaster 是个广播者,其拥有全部的SpringListener实例,然后发布事件的时候调用multicastEvent方法,通过方法列表参数过滤出符合条件的了监听者,监听者触发监听逻辑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342