说说 Spring 的容器事件体系

Spring 的 ApplicationContext 能够发布事件并且允许注册相应的事件监听器,它拥有一套完善的事件发布和监听机制。

在事件体系中有这些概念:

  • 事件: java.util.EventObject。
  • 监听器:java.util.EventListener。
  • 事件源:产生事件,任何一个事件,都必须拥有一个事件源。
  • 事件监听器注册表:用于保存监听器。当事件源产生事件时,就会通知注册表中的监听器。
  • 事件广播器:它负责向监听器通知事件。

有时候,事件源、监听器注册表和事件广播这三个角色可以由同一个对象承担,比如 java.swing 中的 JButton。

事件体系,其实是观察者模式的一种具体实现方式。

1 Spring 事件类体系

1.1 事件类

ApplicationEvent 的唯一一个构造函数是 ApplicationEvent(Object source),通过 source 来指定事件源,它有两个子类:

  • ApplicationContextEvent - 容器事件,它有四个子类,分别表示停止、刷新、关闭和启动容器事件。
  • RequestHandledEvent - 与 Web 应用相关的事件,当处理一个 HTTP 请求后会产生该事件(前提是在 web.xml 中定义了 DispatcherServlet)。

1.2 事件监听器接口

ApplicationListener 接口只定义了 onApplicationEvent(E event) 方法,它接收 ApplicationEvent 事件对象,由实现类在此编写事件的响应处理逻辑。

SmartApplicationListener 是 Spring3.0 新增的接口,它定义两个方法:

方法 说明
boolean supportsEventType(Class<? extends ApplicationEvent> eventType) 指定支持哪些类型的容器事件。
boolean supportsSourceType(Class<?> sourceType) 指定对何种数据源做出响应。

GenericApplicationListener 是 Spring4.2 新增的接口,它增强了对泛型的支持,supportsEventType() 方法的参数采用的是可解析类型 ResolvableType。这是 Spring4 提供的泛型操作支持类,通过它可以很容易地获得泛型的实际类型信息,比如类级、字段级等等泛型信息。在 Spring4 的框架中,很多核心类内部涉及的泛型操作大都使用 ResolvableType 类进行处理。

GenericApplicationListener 定义两个方法,与 SmartApplicationListener 的方法功能相同。

1.3 事件广播器

Spring 为事件广播器定义了一个接口、一个抽象类和一个简单的实现类。

2 分析

ApplicationContext 接口的抽象实现类 AbstractApplicationContext 搭建了事件体系。它包含 ApplicationEventMulticaster 变量,这个变量提供了容器监听器的注册表。而 refresh() 通过以下步骤搭建了事件的基础设施:

  1. 初始化应用上下文的事件广播器 - initApplicationEventMulticaster();
  2. 注册事件监听器 - registerListeners()
  3. 发布容器刷新事件 - finishRefresh()

我们可以在配置文件中自定义一个容器的事件广播器(实现了 ApplicationEventMulticaster),Spring 会利用反射机制将其注册为容器的事件广播器。Spring 如果没有找到定义的事件广播器,将会使用默认的事件广播器(SimpleApplicationEventMulticaster)。

Spring 会利用反射机制,从 BeanDefinitionRegistry 中找出所有实现了 ApplicationListener 的 Bean,将它们都注册为容器的事件监听器。

3 示例

假设我们需要一个短信发送器,它向目的号码发送短信时,会产生一个事件,然后通知给注册了该事件的监听器,进行后续的业务逻辑操作。

SmsSendEvent 事件:

public class SmsSendEvent extends ApplicationContextEvent {

    /**
     * 目的号码
     */
    private String target;

    /**
     * Create a new ContextStartedEvent.
     *
     * @param source the {@code ApplicationContext} that the event is raised for
     *               (must not be {@code null})
     * @param target
     */
    public SmsSendEvent(ApplicationContext source, String target) {
        super(source);
        this.target = target;
    }

    public String getTarget() {
        return target;
    }
}

SmsSendEvent 扩展了 ApplicationContextEvent,加入了一个目的号码 target。

SmsSendListener 监听器会监听 SmsSendEvent 事件:

public class SmsSendListener implements ApplicationListener<SmsSendEvent> {
    public void onApplicationEvent(SmsSendEvent event) {
        System.out.println("向 "+event.getTarget()+" 发送短信");
    }
}

SmsSender 实现了 ApplicationContextAware 接口,因此就具有了发布事件的能力:

public class SmsSender implements ApplicationContextAware {

    private ApplicationContext context;

    /**
     * 容器启动时,注入容器实例
     *
     * @param applicationContext
     * @throws BeansException
     */
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    /**
     * 发送短信
     * @param target
     */
    public void send(String target) {
        System.out.println("准备发送短信");
        SmsSendEvent event = new SmsSendEvent(context, target);
        context.publishEvent(event);//向容器中所有注册了该事件的监听器发送该事件
    }
}

这里通过 ApplicationContext 对象来发送事件,通知相应的事件监听器。

bean 配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <!-- 事件监听器-->
    <bean class="net.deniro.spring4.event.SmsSendListener"/>

    <bean id="SmsSender" class="net.deniro.spring4.event.SmsSender"/>

</beans>

单元测试:

ApplicationContext context;

@BeforeMethod
public void setUp() throws Exception {
    context = new ClassPathXmlApplicationContext("beans.xml");
}

@Test
public void test() {
    SmsSender sender= (SmsSender) context.getBean("SmsSender");
    sender.send("18929293832");
}

容器启动后,就会根据配置文件自动注册 SmsSendListener 监听器啦。

输出结果:

准备发送短信
向 18929293832 发送短信

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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.1 spring IoC容器和beans的简介 Spring 框架的最核心基础的功能是IoC(控制反转)容器,...
    simoscode阅读 6,633评论 2 22
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,359评论 6 343
  • 前言 在微服务架构的系统中,我们通常会使用轻量级的消息代理来构建一个共用的消息主题让系统中所有微服务实例都连接上来...
    Chandler_珏瑜阅读 6,443评论 2 39
  • 希望自己可以认真的过每一天 珍惜现在 平安喜乐
    小藤小藤要加油阅读 275评论 0 0