Spring的事件驱动模型

1. 简单介绍:

事件Event驱动模型实际也被称之为观察者模式,或者发布/订阅模型。
Spring中,也为我们提供了这样的框架,采用Event/Listener这样的好处自然不用多说,就是解耦,利于扩展,并且利于一对多这种形式,我们下面就来介绍下Spring的Event模式:

2. 核心类:

想要了解Spring的事件模型,需要了解一下几个类:

  • ApplicationEvent
    事件本身,继承自Java EventObject。
  • ApplicationListener
    监听者对象,能够选择监听不同的事件Event
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener
  • ApplicationEventMulticaster
    事件广播,也是Spring事件模型中的核心对象,事件发布-监听依靠的就是ApplicationEventMulticaster,其内部类ListenerRetriever按照不同的ApplicationEvent类型对Listener进行了保存,这也是我们为什么能够针对不同的Event进行过滤从而唤醒Listener的过程。
  • ApplicationEventPublisher
    事件的发布者,通过ApplicationEventPublisher#publishEvent的方法进行事件的广播。
  • ApplicationEventPublisherAware
    获取ApplicationEventPublisher的方法。
  • AbstractApplicationContext
    Spring的事件模型框架的主要逻辑的内容出。AbstractApplicationContext中实现了ApplicationEventPublisher接口,从而实际在Spring中,默认的ApplicationEventPublisher实现就是AbstractApplicationContext。

3. 例子

我们以如下的情况为例:
以问答知乎为例,我们在新增一个问题时,需要异步的去建立索引。而建立索引的过程由于和新增的逻辑没有必然的联系,所以可以通过异步的方式来进行,而这种事件的方式能够很好的进行解耦,或者异步来执行,所以我们可以采用事件驱动来实现。

// QuestionEvent
public class QuestionEvent extends ApplicationEvent {
    private String question;
    public String getQuestion() {
        return question;
    }
    public void setQuestion(String question) {
        this.question = question;
    }
    public QuestionEvent(String question) {
        super(question);
        this.question = question;
    }
}

// QuestionListener
@Component("questionListener")
public class QuestionListener implements ApplicationListener<QuestionEvent> {
    public void onApplicationEvent(QuestionEvent questionEvent) {
        System.out.println("index question : " + questionEvent.getQuestion());
    }
}

// EventPublisher
@Component(value = "eventPublisher")
public class EventPublisher implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public ApplicationEventPublisher getApplicationEventPublisher() {
        return applicationEventPublisher;
    }
}

// EventTest
public class EventTest extends BaseTestNG {

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Test
    public void testEvent() {
        String question = "这是一个问题";
        applicationEventPublisher.publishEvent(new QuestionEvent(question));
    }
}

输出:
index question : 这是一个问题

4. 源码分析:

在分析源码之前,我们首先要明白Event-Listener模式实际就是观察者模式。如果看过之前写过的观察者的模式的文章,将会对源码的内容较容易明白。
首先是Listener的注册:
我们定义了Listener(实现了ApplicationListener),那又是什么时候被注册到BeanFactory中呢,如果了解Spring的Bean的生命周期的话,会知道BeanPostProcess是在AbstractApplicationContext的refresh时候被实例化并初始化的,这个过程是要由于非lazy-init Bean的实例化过程的。而实际,Listener的注册过程也是处在refresh的过程中的,我们来看AbstractApplicationContext#refresh的代码:

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // 初始化传播器
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // 注册监听器
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

上面代码中,与Event有关的是两个内容,第一个是initApplicationEventMulticaster(),用于初始化事件传播器,而第二个registerListeners(),便是注册监听器,实际就是扫描实现了ApplicationListener接口的类并注册到事件传播器。
我们来看InitApplicationEventMulticaster:

    protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            this.applicationEventMulticaster =
                    beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
            if (logger.isDebugEnabled()) {
                logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
            }
        }
        else {
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
            if (logger.isDebugEnabled()) {
                logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
                        APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
                        "': using default [" + this.applicationEventMulticaster + "]");
            }
        }
    }

做的内容就是一件事,如果容器中没有name=“applicationEventMulticaster”的类,则认为没有自定义的ApplicationEventMulticaster,此时会自己new一个SimpleApplicationEventMulticaster。

那我们来看看这个默认的广播器的实现SimpleApplicationEventMulticaster,其中核心的方法是multicastEvent,从字面来看是多路广播事件的意思:

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                doInvokeListener(listener, event);
            }
            catch (Throwable err) {
                errorHandler.handleError(err);
            }
        }
        else {
            doInvokeListener(listener, event);
        }
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        }
        catch (ClassCastException ex) {
            String msg = ex.getMessage();
            if (msg == null || matchesClassCastMessage(msg, event.getClass().getName())) {
                // Possibly a lambda-defined listener which we could not resolve the generic event type for
                // -> let's suppress the exception and just log a debug message.
                Log logger = LogFactory.getLog(getClass());
                if (logger.isDebugEnabled()) {
                    logger.debug("Non-matching event type for listener: " + listener, ex);
                }
            }
            else {
                throw ex;
            }
        }
    }

可见multicastEvent是获取对应的Event的listener,并进行调用的,也就是listener.onApplicationEvent(event)。在这里面有点需要注意的是在进行调用的时候,我们会先去获得Executor executor = getTaskExecutor();但是如果我们使用的是默认的Executor的话,则实际executor = null,则此时唤醒的是同步的,如果我们希望采用多线程的方式,则实际需要配置自己事件传播器,并通过setter的方式将executor配置进去。
而关于SimpleApplicationEventMulticaster是如何将Listener配置进去的,下面的代码显示了细节:

public abstract class AbstractApplicationEventMulticaster
        implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {

    final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
...
private class ListenerRetriever {

        public final Set<ApplicationListener<?>> applicationListeners;

        public final Set<String> applicationListenerBeans;

        private final boolean preFiltered;

        public ListenerRetriever(boolean preFiltered) {
            this.applicationListeners = new LinkedHashSet<>();
            this.applicationListenerBeans = new LinkedHashSet<>();
            this.preFiltered = preFiltered;
        }

        public Collection<ApplicationListener<?>> getApplicationListeners() {
            LinkedList<ApplicationListener<?>> allListeners = new LinkedList<>();
            for (ApplicationListener<?> listener : this.applicationListeners) {
                allListeners.add(listener);
            }
            if (!this.applicationListenerBeans.isEmpty()) {
                BeanFactory beanFactory = getBeanFactory();
                for (String listenerBeanName : this.applicationListenerBeans) {
                    try {
                        ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                        if (this.preFiltered || !allListeners.contains(listener)) {
                            allListeners.add(listener);
                        }
                    }
                    catch (NoSuchBeanDefinitionException ex) {
                        // Singleton listener instance (without backing bean definition) disappeared -
                        // probably in the middle of the destruction phase
                    }
                }
            }
            AnnotationAwareOrderComparator.sort(allListeners);
            return allListeners;
        }
    }
...

}

在AbstractApplicationEventMulticaster中定义了一个内部类ListenerRetriever,实际ListenerRetriever对应的是同一类ApplicationEvent的事件监听器,而通过了一个Map当做缓存来取得相应的Listener。
每次获取的时候,会根据EventType和SourceType来生成相应的ListenerCacheKey,从而获得相应的监听器。

5. 定义有序的监听器:

如果我们希望监听器有序的话,实际只要实现SmartApplicationListener接口即可。

6. 参考文章:

详解Spring事件驱动模型
Spring源码

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 事件驱动模型简介 事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;理解它的几个关键点: 1.首先是一种对象...
    algernoon阅读 1,611评论 0 4
  • 前言 在微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都连接上来...
    Chandler_珏瑜阅读 6,442评论 2 39
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,358评论 6 343
  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981阅读 15,475评论 2 11