Spring 中的事件监听机制
admin
2024-02-25 14:29:09
0

目录

1、标准的 Spring 事件机制

(1)ApplicationEvent 自定义事件

(2)ApplicationEventPublisher 发布事件

(3)ApplicationListener 监听事件

2、基于 @EventListener 注解的事件监听器

3、Asynchronous Listeners 异步监听器

4、Ordering Listeners 监听器排序

5、Generic Events 泛型事件


1、标准的 Spring 事件机制

        ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口实现的。如果一个 Bean(监听器实例)实现了 ApplicationListener 接口,并被部署到 Spring 容器中,那么每次在容器中发布一个 ApplicationEvent 事件,该 Bean (监听器实例)都会被通知到。本质上,这种机制就是一个观察者的设计模式。// 事件、监听器、观察者模式。

        在 Spring 4.2 中,事件的基础结构得到了显著的改进,并且提供了基于注解的模型以及发布任意事件的能力(也就是说,一个对象不一定继承 ApplicationEvent ),当这样的对象被发布时,Spring 为你将这个对象自动包装在一个事件中。// 发布一个不继承 ApplicationEvent 的事件?怎样实现,如果不继承 ApplicationEvent 怎么会知道它是一个事件的实例?

        Spring 中提供了一些标准事件(内置事件),了解 Spring 中的内置事件,请点击这里。// 这里简单概述一下,主要有:

  • 容器刷新事件(ContextRefreshedEvent)
  • 容器启动事件(ContextStartedEvent)
  • 容器停止事件(ContextStoppedEvent)
  • 容器关闭事件(ContextClosedEvent)
  • 请求处理事件(RequestHandledEvent)
  • Servlet 请求处理事件(ServletRequestHandledEvent)

(1)ApplicationEvent 自定义事件

        在 Spring 中,你可以创建和发布自己的自定义事件。下面的例子展示了一个继承了 Spring ApplicationEvent 类的自定义事件类:

import org.springframework.context.ApplicationEvent;
public class BlockedListEvent extends ApplicationEvent {private final String address;private final String content;public BlockedListEvent(Object source, String address, String content) {super(source);this.address = address;this.content = content;System.out.println("实例化BlockedListEvent...");}// accessor and other methods...
}

(2)ApplicationEventPublisher 发布事件

        如果要发布一个自定义的 ApplicationEvent 事件,需要调用 ApplicationEventPublisher 中的 publishEvent() 方法。通常情况下,会去创建一个新的类,然后通过实现 ApplicationEventPublisherAware 接口来获取一个默认的 Publisher,最后把该类注册为容器的一个 Bean。下边的例子展示了这样一个类:// 不直接使用 ApplicationContext 调用 publishEvent() 的原因,看起来有种最少知道原则的意思。

public class EmailService implements ApplicationEventPublisherAware {private List blockedList;// 使用事件发布者,与 Spring 容器进行交互private ApplicationEventPublisher publisher;public void setBlockedList(List blockedList) {this.blockedList = blockedList;}public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {this.publisher = publisher; // 自动装配,从 Spring 容器中装配一个发布者到 EmailService}public void sendEmail(String address, String content) {if (blockedList.contains(address)) {// 发布事件System.out.println("publish BlockedListEvent...");publisher.publishEvent(new BlockedListEvent(this, address, content));return;}// send email...}
}

        在上边例子中,Spring 容器会为 setApplicationEventPublisher() 方法自动注入 ApplicationEventPublisher 实例( Publisher 是 Spring 提供的,我们暂时不用关注),获取 ApplicationEventPublisher 后,我们可以通过 ApplicationEventPublisher 与 Spring 容器进行交互。// 对于 Spring 中 Publisher 的具体实现,在源码分析中,聊到了很多多播器,Spring 默认使用 SimpleApplicationEventMulticaster 多播器。

(3)ApplicationListener 监听事件

        为了接收自定义的 ApplicationEvent 事件,需要创建一个监听器。新建一个类实现 ApplicationListener 接口,并将该类注册为 Spring bean。下边的例子展示了这样一个类:

public class BlockedListNotifier implements ApplicationListener {// 泛型参数化private String notificationAddress; // 通知地点,一个简单的属性public void setNotificationAddress(String notificationAddress) {this.notificationAddress = notificationAddress;}@Override // 默认情况下,监听器同步接受事件public void onApplicationEvent(BlockedListEvent event) {// notify appropriate parties via notificationAddress...System.out.println("BlockedListNotifier监听到目标事件:" + notificationAddress);}
}

        你可以注册任意数量的监听器,但是这些监听器默认都是同步阻塞的,这意味着调用 publisher 中的 publishEvent() 方法会被阻塞,直到所有的监听器执行完操作逻辑。同步的好处是,如果 Spring 开启了事务,那么这些操作都会在一个事务中进行。其他发布策略,可以具体查看  ApplicationEventMulticaster 和 SimpleApplicationEventMulticaster 这两个接口的相关文档。

        下面的示例显示了用于注册和配置上述每个类的 bean 定义:// Bean.xml 传统的 xml 方式


known.spammer@example.orgknown.hacker@example.orgjohn.doe@example.org

        定义完 bean.xml 文件后,每当调用 emailService 中的 sendEmail() 方法时,如果有任何应该被阻止的电子邮件消息,都会发布一个 BlockedListEvent 类型的自定义事件。另外,blockedListNotifier 被注册为 ApplicationListener 实例并接收 BlockedListEvent 事件,此时 blockedListNotifier 可以通知它想通知的任何对象。// 当一个事件发生时,监听器可以操作一些自己的逻辑

public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");EmailService service = (EmailService) context.getBean("emailService");service.sendEmail("john.doe@example.org", "发送邮件的内容...");}

        Spring 事件机制,被设计用来实现同一个 Spring 容器中的 Beans 能够进行简单的互动。但是,对于更复杂的企业级集成需求,可以使用单独维护的 Spring integration 项目,该项目为构建轻量级的、面向模式的、事件驱动的体系结构提供了完整的支持,这些体系结构都构建在 Spring 编程模型之上。// 事件机制就是为了满足 beans 之间能够实现简单的互动

2、基于 @EventListener 注解的事件监听器

        // 只是对监听器的实现,并没有提供事件实现的注解,所以定义一个事件,还是需要继承 ApplicationEvent

        你可以使用 @EventListener 注解在一个类的任何方法上注册一个事件监听器。BlockedListNotifier 可以重写如下: // 这个类同样需要被实例化

public class BlockedListNotifier {private String notificationAddress;public void setNotificationAddress(String notificationAddress) {this.notificationAddress = notificationAddress;}// 默认情况下,监听器同步接受事件@EventListenerpublic void onApplicationEvent(BlockedListEvent event) {System.out.println("BlockedListNotifier监听到目标事件:"+ notificationAddress);}
}

        被 @EventListener 注解的方法,它的参数声明了它侦听的事件类型,通过使用注解,可以使用灵活的监听器名称(方法名称随便定义),并且不用实现特定的侦听器接口。

        如果你的的方法想监听多个事件,或者想在定义方法时不使用任何参数,那么也可以在注解上指定事件的类型。下面的例子展示了如何做到这一点:

@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {// ...
}

        还可以添加额外的条件来限制事件的监听,具体操作请看官方文档。

        如果你需要发布一个事件作为处理另一个事件的结果,你可以改变方法签名来返回应该发布的事件(这个特征不支持异步监听),如下例所示:

    @EventListenerpublic AnotherBlockedListEvent onApplicationEvent(BlockedListEvent event) {// 返回一个事件,该事件的监听器也会被通知到,不需要额外的发布操作return new AnotherBlockedListEvent(this,"john2.doe@example.org","my-event2");}

        onApplicationEvent() 方法为它处理的每个 BlockedListEvent 事件,都会发布一个新的 AnotherBlockedListEvent 事件。如果需要发布多个事件,则可以返回一个 Collection 或事件数组。// 这个特征很有意思,可以一环套一环的通知下去,做成一个通知链。

3、Asynchronous Listeners 异步监听器

        如果你想使一个特定的侦听器异步处理事件,你可以使用常规的 @Async 支持。下面的例子展示了如何做到这一点:

@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {// BlockedListEvent is processed in a separate thread
}

使用异步事件时要注意以下限制:

  • 如果异步事件监听器抛出异常,则不会将其传播给调用者。更多细节请参见 AsyncUncaughtExceptionHandler。
  • 异步事件监听器方法不能通过返回值来发布后续事件。如果你需要作为处理的结果发布另一个事件,请注入一个 ApplicationEventPublisher 来手动发布事件。

4、Ordering Listeners 监听器排序

        如果你需要一个监听器在另一个监听器之前被调用,你可以在方法声明中添加 @Order 注释,如下面的例子所示:// @Order 注释,值越低优先级越高,用来对 Spring components(组件)排序

@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {// notify appropriate parties via notificationAddress...
}

5、Generic Events 泛型事件

        根据一组对象中的特定类型来触发事件。需要有额外的配置。详情请参考官方文档。

总结:主要介绍了 Spring 中的事件机制,典型的观察者模式,该机制在 Spring Boot 中很重要,开发中用来设计架构也很常用,需要重点掌握。

相关内容

热门资讯

帮忙推荐下长沙的酒店 帮忙推荐下长沙的酒店本人学生 经济能力有限,打算这个月去长沙玩。麻烦推荐一些酒店吧,不用太高档的,大...
关于星空, 星星的优美句子, ... 关于星空, 星星的优美句子, 可以是名言名句, 我写作文!急需!少来抄袭的我高三, 不要拿小学生的来...
吃东西时 觉得食管很堵 食物下... 吃东西时 觉得食管很堵 食物下咽困难 而且经常胸口痛到医院诊断再决定治疗方案这个情况可能是食道或者食...
怎样修炼海底轮? 怎样修炼海底轮?使用海底之光加上七个火龙珠就可以在火炉里面修炼了。如果这个脉轮被阻塞,安全感就会被怀...
白山黑水指的是哪里? 白山黑水指的是哪里?白山指的是长白山黑水指的是黑龙江,其实吉林是没有黑龙江的,但松花江最后注入的就是...
婚姻是什么呢?对的人没有争吵只... 婚姻是什么呢?对的人没有争吵只有爱,错的人就是地狱火海 如果不是你,我宁愿孤独终老。婚姻是什么呢?对...
重赏!我要临界爵迹3风津道到目... 重赏!我要临界爵迹3风津道到目前的所有章回,全文发给我,不能有错别字。我自己刚刚找了下比较全的 我...
求道教书籍 最好与修炼有关的 求道教书籍 最好与修炼有关的去读陈樱宁先生的书, 古书隐语太多,非有明师指点 《 道学通论》也不错...
美容超声刀的副作用? 美容超声刀的副作用?你好!美容超声刀对祛除面部皱纹、紧致皮肤有着良好的效果,但是它有很多副作用;它的...
简单的用气球做一个东西 简单的用气球做一个东西用气球制作各种有趣的玩具和小装饰品是很有趣的手工活动之一。在这里,我将介绍如何...
拉酒线就一定能判断白酒的好坏吗... 酒桌上总有人端着酒杯表演 “拉酒线”:酒瓶一抬,细长的酒线像银线似的坠入杯中,酒花细密持久,围观者立...
核桃油凉拌娃娃菜——脆嫩口感与... 核桃油凉拌娃娃菜创新做法,既保留娃娃菜的脆嫩口感,又突出核桃油的坚果香气,营养与风味兼具: 食材:...
原创 国... #国外的这个早餐,非常值得中国学习!好吃、润肠、补钙、降血糖! 在探寻各国饮食文化的过程中,我发现...
15句适合早上发的早安句子,每... 1、笑而不语,是成长;痛而不言,是经历。一辈子的事,急不来,慢慢品。以清净心看世界,以欢喜心过生活。...
炎热的夏天怎么吃?这三道简单易... 酷暑来袭,气温逼近40℃,武汉连续发布高温预警。夏季高温湿热,很多人可能会食欲不振或消化不良。同时,...
特发性震颤,夏季必备的食物禁忌 特发性震颤,中医多将其归为 “颤证” 范畴,认为其发病与肝肾阴虚、虚风内动密切相关。 夏季暑气旺盛,...
辽宁红烧鲫鱼:浓油赤酱里的本帮... 当辽宁的烟火气与上海本帮菜的“浓油赤酱”相遇,一道融合地域特色的红烧鲫鱼便承载了独特的文化情怀。这道...
五歲孩子給小朋打不還手 作為媽... 五歲孩子給小朋打不還手 作為媽咪教孩子學會保擭自已「孩子不能關養」 要懂得還手 為此我同先生意見分歧...
一叶障目讲的什么故事? 一叶障目讲的什么故事?楚国有个人家境破落,生活贫困。妻妾都劝他读书考取功名,但是他嫌读书太累,不想读...