Spring核心——全局事件管理

ApplicationContext是一个Context策略(见上下文与IoC),他除了提供最基础的IoC容器功能,还提供了MessageSource实现的国际化、全局事件、资源层级管理等等功能。本文将详细介绍Spring核心模块的事件管理机制。

Spring核心模块的事件机制和常规意义上的“事件”并没有太大区别(例如浏览器上的用户操作事件)都是通过订阅/发布模式实现的。

Spring事件管理的内容包括标准事件、自定义事件、注解标记处理器、异步事件处理、通用实体包装。

我们都知道在订阅/发布模式中至少要涉及三个部分——发布者(publisher)、订阅者(listener/subscriber)和事件(event)。针对这个模型Spring也提供了对应的两个接口——ApplicationEventPublisher、ApplicationListener以及一个抽象类ApplicationEvent。基本上,要使用Spring事件的功能,只要实现/继承这这三个接口/抽象类并按照Spring定好的规则来使用即可。掌握这个原则那么接下来的内容就好理解了。

标准事件

Spring为一些比较常规的事件制定了标准的事件类型和固定的发布方法,我们只需要定制好订阅者(listener/subscriber)就可以监听这些事件。

先指定2个订阅者:

packagechkui.springcore.example.javabase.event.standard;publicclassContextStartedListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(ContextStartedEvent event){System.out.println("Start Listener: I am start");}}

packagechkui.springcore.example.javabase.event.standard;publicclassContextStopListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(ContextStoppedEvent event){System.out.println("Stop Listener: I am stop");}}

 然后运行使用他们:

packagechkui.springcore.example.javabase.event;@ConfigurationpublicclassEventApp{@BeanContextStopListenercontextStopListener(){returnnewContextStopListener();}@BeanContextStartedListenercontextStartedListener(){returnnewContextStartedListener();}publicstaticvoidmain(String[] args){ConfigurableApplicationContext context =newAnnotationConfigApplicationContext(EventApp.class);//发布start事件context.start();//发布stop事件context.stop();//关闭容器context.close();}}

在例子代码中,ContextStartedListenerContextStopListener类都实现了ApplicationListener接口,然后通过onApplicationEvent的方法参数来指定监听的事件类型。在ConfigurableApplicationContext接口中已经为“start”和“stop”事件提供对应的发布方法。除了StartedEventStoppedEventSpring还为其他几项操作提供了标准事件:

ContextRefreshedEvent:ConfigurableApplicationContext::refresh方法被调用后触发。事件发出的时机是所有的后置处理器已经执行、所有的Bean已经被加载、所有的ApplicationContext接口方法都可以提供服务。

ContextStartedEvent:ConfigurableApplicationContext::start方法被调用后触发。

ContextStoppedEvent:ConfigurableApplicationContext::stop方法被调用后触发。

ContextClosedEvent:ConfigurableApplicationContext::close方法被调用后触发。

RequestHandledEvent:这是一个用于Web容器的事件(例如启用了DispatcherServlet),当接收到前端请求时触发。

自定义事件

除了使用标准事件,我们还可以定义各种各样的事件。实现前面提到的三个接口/抽象类即可。

继承ApplicationEvent实现自定义事件:

packagechkui.springcore.example.javabase.event.custom;publicclassMyEventextendsApplicationEvent{privateString value ="This is my event!";publicMyEvent(Object source,String value){super(source);this.value = value;}publicStringgetValue(){returnvalue;}}

定义事件对应的Listener:

packagechkui.springcore.example.javabase.event.custom;publicclassMyEventListenerimplementsApplicationListener{publicvoidonApplicationEvent(MyEvent event){System.out.println("MyEventListener :"+ event.getValue());}}

然后通过ApplicationEventPublisher接口发布事件:

packagechkui.springcore.example.javabase.event.custom;@ServicepublicclassMyEventServiceimplementsApplicationEventPublisherAware{privateApplicationEventPublisher publisher;@OverridepublicvoidsetApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher){publisher = applicationEventPublisher;}publicvoidpublish(String value){publisher.publishEvent(newMyEvent(this, value));}}

使用@EventListener实现订阅者

Spring Framework4.2之后可以直接使用@EventListener注解来指定事件的处理器,我们将上面的MyEventListener类进行简单的修改:

packagechkui.springcore.example.javabase.event.custom;publicclassMyEventListenerAnnotation{@EventListenerpublicvoidhandleMyEvent(MyEvent event){System.out.println("MyEventListenerAnnotation :"+ event.getValue());    }}

使用@EventListener可以不必实现ApplicationListener,只要添加为一个Bean即可。Spring会根据方法的参数类型订阅对应的事件。

我们也可以使用注解指定绑定的事件:

packagechkui.springcore.example.javabase.event.custom;publicclassMyEventListenerAnnotation{@EventListener(ContextStartedEvent.class})publicvoidhandleMyEvent(){//----}}

 还可以指定一次性监听多个事件:

packagechkui.springcore.example.javabase.event.standard;publicclassMultiEventListener{@EventListener({ContextStartedEvent.class, ContextStoppedEvent.class})@Order(2)voidcontenxtStandadrEventHandle(ApplicationContextEvent event){System.out.println("MultiEventListener:"+ event.getClass().getSimpleName());}}

注意上面代码中的@Order注解,同一个事件可以被多个订阅者订阅。在多个定于者存在的情况下可以使用@Order注解来指定他们的执行顺序,数值越小越优先执行。

EL表达式设定事件监听的条件

通过注解还可以使用SpringEL表达式来更细粒度的控制监听的范围,比如下面的例子仅仅当事件的实例中MyEvent.value == "Second publish!"才触发处理器:

事件:

packagechkui.springcore.example.javabase.event.custom;publicclassMyEventextendsApplicationEvent{privateString value ="This is my event!";publicMyEvent(Object source,String value){super(source);this.value = value;}publicStringgetValue(){returnvalue;}}

通过EL表达式指定监听的数据:

packagechkui.springcore.example.javabase.event.custom;publicclassMyEventListenerElSp{@EventListener(condition="#p0.value == 'Second publish!'")publicvoidhandleMyEvent(MyEvent event){System.out.println("MyEventListenerElSp :"+ event.getValue());    }}

这样,当这个事件被发布,而且其中的成员变量value值等于"Second publish!",对应的MyEventListenerElSp::handleMyEvent方法才会被触发。EL表达式还可以使用通配符等等丰富的表现形式来设定过滤规则,后续介绍EL表达式时会详细说明。

通用包装事件

Spring还提供一个方式使用事件来包装实体类,起到传递数据但是不用重复定义多个事件的作用。看下面的例子。

我们先定义2个实体类:

packagechkui.springcore.example.javabase.event.generics;classPES{publicStringtoString(){return"PRO EVOLUTION SOCCER";}}classWOW{publicStringtoString(){return"World Of Warcraft";}}

定义可以用于包装任何实体的事件,需要实现ResolvableTypeProvider接口:

packagechkui.springcore.example.javabase.event.generics;publicclassEntityWrapperEventextendsApplicationEventimplementsResolvableTypeProvider{publicEntityWrapperEvent(T entity){super(entity);}publicResolvableTypegetResolvableType(){returnResolvableType.forClassWithGenerics(getClass(),                ResolvableType.forInstance(getSource()));}}

订阅者可以根据被包裹的entity的不同来监听不同的事件:

packagechkui.springcore.example.javabase.event.generics;publicclassEntiryWrapperEventListener{@EventListenerpublicvoidhandlePES(EntityWrapperEvent<PES> evnet){System.out.println("EntiryWrapper PES: "+  evnet);}@EventListenerpublicvoidhandleWOW(EntityWrapperEvent<WOW> evnet){System.out.println("EntiryWrapper WOW: "+  evnet);}}

上面的代码起到最用的主要是ResolvableType.forInstance(getSource())这一行代码,getSource()方法来自于EventObject类,它实际上就是返回构造方法中super(entity)设定的entity实例。

写在最后的

订阅/发布模式是几乎所有软件程序都会触及的问题,无论是浏览器前端、还是古老的winMFC程序。而在后端应用中,对于使用过MQ工具或者Vertx这种纯事件轮询驱动的框架码友,应该已经请清楚这种订阅/发布+事件驱动的价值。它除了能够降低各层的耦合度,还能更有效的利用多线程而大大的提执行效率(当然对开发人员的要求也会高不少)。

对于Spring核心框架来说,事件的订阅/发布只是IoC容器的一个附属功能,Spring的核心价值并不在这个地方。Spring的订阅发布功能在实现层面至少现在并没有使用EventLoop的方式,还是类与类之间的直接调用,所以在性能上是完全无法向Vertx看齐的。不过Spring事件的机制还是能够起到事件驱动的效果,可以用来全局控制一些状态。如果选用Spring生态中的框架(boot等)作为我们的底层框架,现阶段还是应该使用IoC的方式来组合功能,而事件的订阅/发布仅仅用于辅助。

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

推荐阅读更多精彩内容

  • 前面我们讲到了Spring在进行事务逻辑织入的时候,无论是事务开始,提交或者回滚,都会触发相应的事务事件。本文首先...
    AI乔治阅读 1,740评论 0 1
  • http://liuxing.info/2017/06/30/Spring%20AMQP%E4%B8%AD%E6%...
    sherlock_6981阅读 15,958评论 2 11
  • 1 前言 本次我们来学习 Spring 的事件处理,源于实际工作中遇到的项目需求:在一个支付的下单场景中,当用户真...
    闻人的技术博客阅读 1,259评论 0 1
  • 浑浑沌沌的过了个年,现在终于步入了正轨,又找到了生活的感觉。每天早晨五点半起,六点半上班,下午6点20准时到...
    黎海静阅读 429评论 0 0
  • “优秀是一种习惯”,这句话激励过我艰难的求学时代,也敦促着我十一年的教学之路。自我要求,不断学习似乎成为...
    藤藤儿_1125阅读 528评论 1 3