ApplicationContext是一个Context策略(见上下文与IoC),他除了提供最基础的IoC容器功能,还提供了MessageSource实现的国际化、全局事件、资源层级管理等等功能。本文将详细介绍Spring核心模块的事件管理机制。前端
Spring核心模块的事件机制和常规意义上的“事件”并无太大区别(例如浏览器上的用户操做事件)都是经过订阅/发布模式实现的。java
Spring事件管理的内容包括标准事件、自定义事件、注解标记处理器、异步事件处理、通用实体包装。下面将经过几个例子来讲明这些内容,可执行代码请到本人的gitee库下载,本文的内容在包chkui.springcore.example.javabase.event中。git
咱们都知道在订阅/发布模式中至少要涉及三个部分——发布者(publisher)、订阅者(listener/subscriber)和事件(event)。针对这个模型Spring也提供了对应的两个接口——ApplicationEventPublisher、ApplicationListener以及一个抽象类ApplicationEvent。基本上,要使用Spring事件的功能,只要实现/继承这这三个接口/抽象类并按照Spring定好的规则来使用便可。掌握这个原则那么接下来的内容就好理解了。spring
Spring为一些比较常规的事件制定了标准的事件类型和固定的发布方法,咱们只须要定制好订阅者(listener/subscriber)就能够监听这些事件。后端
先指定2个订阅者:浏览器
package chkui.springcore.example.javabase.event.standard; public class ContextStartedListener implements ApplicationListener<ContextStartedEvent> { @Override public void onApplicationEvent(ContextStartedEvent event) { System.out.println("Start Listener: I am start"); } }
package chkui.springcore.example.javabase.event.standard; public class ContextStopListener implements ApplicationListener<ContextStoppedEvent> { @Override public void onApplicationEvent(ContextStoppedEvent event) { System.out.println("Stop Listener: I am stop"); } }
而后运行使用他们:多线程
package chkui.springcore.example.javabase.event; @Configuration public class EventApp { @Bean ContextStopListener contextStopListener() { return new ContextStopListener(); } @Bean ContextStartedListener contextStartedListener() { return new ContextStartedListener(); } public static void main(String[] args) { ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(EventApp.class); //发布start事件 context.start(); //发布stop事件 context.stop(); //关闭容器 context.close(); } }
在例子代码中,ContextStartedListener和ContextStopListener类都实现了ApplicationListener接口,而后经过onApplicationEvent的方法参数来指定监听的事件类型。在ConfigurableApplicationContext接口中已经为“start”和“stop”事件提供对应的发布方法。除了StartedEvent和StoppedEvent,Spring还为其余几项操做提供了标准事件:app
除了使用标准事件,咱们还能够定义各类各样的事件。实现前面提到的三个接口/抽象类便可。框架
继承ApplicationEvent实现自定义事件:异步
package chkui.springcore.example.javabase.event.custom; public class MyEvent extends ApplicationEvent { private String value = "This is my event!"; public MyEvent(Object source,String value) { super(source); this.value = value; } public String getValue() { return value; } }
定义事件对应的Listener:
package chkui.springcore.example.javabase.event.custom; public class MyEventListener implements ApplicationListener<MyEvent> { public void onApplicationEvent(MyEvent event) { System.out.println("MyEventListener :" + event.getValue()); } }
而后经过ApplicationEventPublisher接口发布事件:
package chkui.springcore.example.javabase.event.custom; @Service public class MyEventService implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { publisher = applicationEventPublisher; } public void publish(String value) { publisher.publishEvent(new MyEvent(this, value)); } }
在Spring Framework4.2以后能够直接使用@EventListener注解来指定事件的处理器,咱们将上面的MyEventListener类进行简单的修改:
package chkui.springcore.example.javabase.event.custom; public class MyEventListenerAnnotation{ @EventListener public void handleMyEvent(MyEvent event) { System.out.println("MyEventListenerAnnotation :" + event.getValue()); } }
使用@EventListener能够没必要实现ApplicationListener,只要添加为一个Bean便可。Spring会根据方法的参数类型订阅对应的事件。
咱们也可使用注解指定绑定的事件:
package chkui.springcore.example.javabase.event.custom; public class MyEventListenerAnnotation{ @EventListener(ContextStartedEvent.class}) public void handleMyEvent() { //---- } }
还能够指定一次性监听多个事件:
package chkui.springcore.example.javabase.event.standard; public class MultiEventListener { @EventListener({ContextStartedEvent.class, ContextStoppedEvent.class}) @Order(2) void contenxtStandadrEventHandle(ApplicationContextEvent event) { System.out.println("MultiEventListener:" + event.getClass().getSimpleName()); } }
注意上面代码中的@Order注解,同一个事件能够被多个订阅者订阅。在多个定于者存在的状况下可使用@Order注解来指定他们的执行顺序,数值越小越优先执行。
经过注解还可使用Spring的EL表达式来更细粒度的控制监听的范围,好比下面的例子仅仅当事件的实例中MyEvent.value == "Second publish!"才触发处理器:
事件:
package chkui.springcore.example.javabase.event.custom; public class MyEvent extends ApplicationEvent { private String value = "This is my event!"; public MyEvent(Object source,String value) { super(source); this.value = value; } public String getValue() { return value; } }
经过EL表达式指定监听的数据:
package chkui.springcore.example.javabase.event.custom; public class MyEventListenerElSp { @EventListener(condition="#p0.value == 'Second publish!'") public void handleMyEvent(MyEvent event) { System.out.println("MyEventListenerElSp :" + event.getValue()); } }
这样,当这个事件被发布,并且其中的成员变量value值等于"Second publish!",对应的MyEventListenerElSp::handleMyEvent方法才会被触发。EL表达式还可使用通配符等等丰富的表现形式来设定过滤规则,后续介绍EL表达式时会详细说明。
Spring还提供一个方式使用事件来包装实体类,起到传递数据可是不用重复定义多个事件的做用。看下面的例子。
咱们先定义2个实体类:
package chkui.springcore.example.javabase.event.generics; class PES { public String toString() { return "PRO EVOLUTION SOCCER"; } } class WOW { public String toString() { return "World Of Warcraft"; } }
定义能够用于包装任何实体的事件,须要实现ResolvableTypeProvider接口:
package chkui.springcore.example.javabase.event.generics; public class EntityWrapperEvent<T> extends ApplicationEvent implements ResolvableTypeProvider { public EntityWrapperEvent(T entity) { super(entity); } public ResolvableType getResolvableType() { return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource())); } }
订阅者能够根据被包裹的entity的不一样来监听不一样的事件:
package chkui.springcore.example.javabase.event.generics; public class EntiryWrapperEventListener { @EventListener public void handlePES(EntityWrapperEvent<PES> evnet) { System.out.println("EntiryWrapper PES: " + evnet); } @EventListener public void handleWOW(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的方式来组合功能,而事件的订阅/发布仅仅用于辅助。