_ApplicationContext_是一个_Context_策略(见上下文与IoC),他除了提供最基础的_IoC_容器功能,还提供了MessageSource实现的国际化、全局事件、资源层级管理等等功能。本文将详细介绍Spring核心模块的事件管理机制。
_Spring_核心模块的事件机制和常规意义上的“事件”并没有太大区别(例如浏览器上的用户操作事件)都是通过订阅/发布模式实现的。
_Spring_事件管理的内容包括标准事件、自定义事件、注解标记处理器、异步事件处理、通用实体包装。下面将通过几个例子来说明这些内容,可执行代码请到本人的gitee库下载,本文的内容在包_chkui.springcore.example.javabase.event_中。
我们都知道在订阅/发布模式中至少要涉及三个部分——发布者(publisher)、订阅者(listener/subscriber)和事件(event)。针对这个模型Spring也提供了对应的两个接口——ApplicationEventPublisher、ApplicationListener_以及一个抽象类_ApplicationEvent。基本上,要使用_Spring_事件的功能,只要_实现/继承_这这三个_接口/抽象类_并按照Spring定好的规则来使用即可。掌握这个原则那么接下来的内容就好理解了。
标准事件
_Spring_为一些比较常规的事件制定了标准的事件类型和固定的发布方法,我们只需要定制好订阅者(listener/subscriber)就可以监听这些事件。
先指定2个订阅者:
1package chkui.springcore.example.javabase.event.standard; 2public class ContextStartedListener implements ApplicationListener<ContextStartedEvent> { 3 @Override 4 public void onApplicationEvent(ContextStartedEvent event) { 5 System.out.println("Start Listener: I am start"); 6 } 7} 8 9package chkui.springcore.example.javabase.event.standard; 10public class ContextStopListener implements ApplicationListener<ContextStoppedEvent> { 11 @Override 12 public void onApplicationEvent(ContextStoppedEvent event) { 13 System.out.println("Stop Listener: I am stop"); 14 } 15}
然后运行使用他们:
1package chkui.springcore.example.javabase.event; 2@Configuration 3public class EventApp { 4 5 @Bean 6 ContextStopListener contextStopListener() { 7 return new ContextStopListener(); 8 } 9 10 @Bean 11 ContextStartedListener contextStartedListener() { 12 return new ContextStartedListener(); 13 } 14 15 public static void main(String[] args) { 16 ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(EventApp.class); 17 //发布start事件 18 context.start(); 19 //发布stop事件 20 context.stop(); 21 //关闭容器 22 context.close(); 23 } 24}
在例子代码中,ContextStartedListener_和_ContextStopListener_类都实现了ApplicationListener接口,然后通过_onApplicationEvent_的方法参数来指定监听的事件类型。在_ConfigurableApplicationContext_接口中已经为“start”和“stop”事件提供对应的发布方法。除了_StartedEvent_和_StoppedEvent,_Spring_还为其他几项操作提供了标准事件:
- ContextRefreshedEvent:ConfigurableApplicationContext::refresh方法被调用后触发。事件发出的时机是所有的后置处理器已经执行、所有的Bean已经被加载、所有的ApplicationContext接口方法都可以提供服务。
- ContextStartedEvent:ConfigurableApplicationContext::start方法被调用后触发。
- ContextStoppedEvent:ConfigurableApplicationContext::stop方法被调用后触发。
- ContextClosedEvent:ConfigurableApplicationContext::close方法被调用后触发。
- RequestHandledEvent:这是一个用于Web容器的事件(例如启用了DispatcherServlet),当接收到前端请求时触发。
自定义事件
除了使用标准事件,我们还可以定义各种各样的事件。实现前面提到的三个接口/抽象类即可。
继承_ApplicationEvent_实现自定义事件:
1package chkui.springcore.example.javabase.event.custom; 2public class MyEvent extends ApplicationEvent { 3 4 private String value = "This is my event!"; 5 6 public MyEvent(Object source,String value) { 7 super(source); 8 this.value = value; 9 } 10 11 public String getValue() { 12 return value; 13 } 14}
定义事件对应的_Listener_:
1package chkui.springcore.example.javabase.event.custom; 2public class MyEventListener implements ApplicationListener<MyEvent> { 3 public void onApplicationEvent(MyEvent event) { 4 System.out.println("MyEventListener :" + event.getValue()); 5 } 6}
然后通过_ApplicationEventPublisher_接口发布事件:
1package chkui.springcore.example.javabase.event.custom; 2@Service 3public class MyEventService implements ApplicationEventPublisherAware { 4 private ApplicationEventPublisher publisher; 5 @Override 6 public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { 7 publisher = applicationEventPublisher; 8 } 9 10 public void publish(String value) { 11 publisher.publishEvent(new MyEvent(this, value)); 12 } 13}
使用@EventListener实现订阅者
在_Spring Framework4.2_之后可以直接使用_@EventListener_注解来指定事件的处理器,我们将上面的_MyEventListener_类进行简单的修改:
1package chkui.springcore.example.javabase.event.custom; 2public class MyEventListenerAnnotation{ 3 @EventListener 4 public void handleMyEvent(MyEvent event) { 5 System.out.println("MyEventListenerAnnotation :" + event.getValue()); 6 } 7}
使用_@EventListener_可以不必实现_ApplicationListener_,只要添加为一个_Bean_即可。_Spring_会根据方法的参数类型订阅对应的事件。
我们也可以使用注解指定绑定的事件:
1package chkui.springcore.example.javabase.event.custom; 2public class MyEventListenerAnnotation{ 3 @EventListener(ContextStartedEvent.class}) 4 public void handleMyEvent() { 5 //---- 6 } 7}
还可以指定一次性监听多个事件:
1package chkui.springcore.example.javabase.event.standard; 2public class MultiEventListener { 3 @EventListener({ContextStartedEvent.class, ContextStoppedEvent.class}) 4 @Order(2) 5 void contenxtStandadrEventHandle(ApplicationContextEvent event) { 6 System.out.println("MultiEventListener:" + event.getClass().getSimpleName()); 7 } 8}
注意上面代码中的_@Order注解,同一个事件可以被多个订阅者订阅。在多个定于者存在的情况下可以使用@Order_注解来指定他们的执行顺序,数值越小越优先执行。
EL表达式设定事件监听的条件
通过注解还可以使用_Spring_的_EL_表达式来更细粒度的控制监听的范围,比如下面的例子仅仅当事件的实例中MyEvent.value == "Second publish!"才触发处理器:
事件:
1package chkui.springcore.example.javabase.event.custom; 2public class MyEvent extends ApplicationEvent { 3 private String value = "This is my event!"; 4 public MyEvent(Object source,String value) { 5 super(source); 6 this.value = value; 7 } 8 9 public String getValue() { 10 return value; 11 } 12}
通过EL表达式指定监听的数据:
1package chkui.springcore.example.javabase.event.custom; 2public class MyEventListenerElSp { 3 @EventListener(condition="#p0.value == 'Second publish!'") 4 public void handleMyEvent(MyEvent event) { 5 System.out.println("MyEventListenerElSp :" + event.getValue()); 6 } 7}
这样,当这个事件被发布,而且其中的成员变量value值等于"Second publish!",对应的MyEventListenerElSp::handleMyEvent方法才会被触发。EL表达式还可以使用通配符等等丰富的表现形式来设定过滤规则,后续介绍EL表达式时会详细说明。
通用包装事件
Spring还提供一个方式使用事件来包装实体类,起到传递数据但是不用重复定义多个事件的作用。看下面的例子。
我们先定义2个实体类:
1package chkui.springcore.example.javabase.event.generics; 2class PES { 3 public String toString() { 4 return "PRO EVOLUTION SOCCER"; 5 } 6} 7class WOW { 8 public String toString() { 9 return "World Of Warcraft"; 10 } 11}
定义可以用于包装任何实体的事件,需要实现ResolvableTypeProvider接口:
1package chkui.springcore.example.javabase.event.generics; 2public class EntityWrapperEvent<T> extends ApplicationEvent implements ResolvableTypeProvider { 3 4 public EntityWrapperEvent(T entity) { 5 super(entity); 6 } 7 8 public ResolvableType getResolvableType() { 9 return ResolvableType.forClassWithGenerics(getClass(), 10 ResolvableType.forInstance(getSource())); 11 } 12 13}
订阅者可以根据被包裹的entity的不同来监听不同的事件:
1package chkui.springcore.example.javabase.event.generics; 2public class EntiryWrapperEventListener { 3 @EventListener 4 public void handlePES(EntityWrapperEvent<PES> evnet) { 5 System.out.println("EntiryWrapper PES: " + evnet); 6 } 7 @EventListener 8 public void handleWOW(EntityWrapperEvent<WOW> evnet) { 9 System.out.println("EntiryWrapper WOW: " + evnet); 10 } 11}
上面的代码起到最用的主要是ResolvableType.forInstance(getSource())这一行代码,getSource()方法来自于EventObject类,它实际上就是返回构造方法中super(entity)设定的entity实例。
写在最后的
订阅/发布模式是几乎所有软件程序都会触及的问题,无论是浏览器前端、还是古老的winMFC程序。而在后端应用中,对于使用过MQ工具或者Vertx这种纯事件轮询驱动的框架码友,应该已经请清楚这种订阅/发布+事件驱动的价值。它除了能够降低各层的耦合度,还能更有效的利用多线程而大大的提执行效率(当然对开发人员的要求也会高不少)。
对于Spring核心框架来说,事件的订阅/发布只是IoC容器的一个附属功能,Spring的核心价值并不在这个地方。Spring的订阅发布功能在实现层面至少现在并没有使用EventLoop的方式,还是类与类之间的直接调用,所以在性能上是完全无法向Vertx看齐的。不过Spring事件的机制还是能够起到事件驱动的效果,可以用来全局控制一些状态。如果选用Spring生态中的框架(boot等)作为我们的底层框架,现阶段还是应该使用IoC的方式来组合功能,而事件的订阅/发布仅仅用于辅助。