我们都知道在订阅/发布模式中至少要涉及三个部分——发布者(
标准事件
先指定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();
}
}在例子代码中,
- ContextRefreshedEvent:ConfigurableApplicationContext::refresh方法被调用后触发。事件发出的时机是所有的后置处理器已经执行、所有的Bean已经被加载、所有的ApplicationContext接口方法都可以提供服务。
- ContextStartedEvent:ConfigurableApplicationContext::start方法被调用后触发。
- ContextStoppedEvent:ConfigurableApplicationContext::stop方法被调用后触发。
- ContextClosedEvent:ConfigurableApplicationContext::close方法被调用后触发。
- RequestHandledEvent:这是一个用于Web容器的事件(例如启用了DispatcherServlet),当接收到前端请求时触发。
自定义事件
除了使用标准事件,我们还可以定义各种各样的事件。实现前面提到的三个接口/抽象类即可。
继承
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;
}
}定义事件对应的
package chkui.springcore.example.javabase.event.custom;
public class MyEventListener implements ApplicationListener<MyEvent> {
public void onApplicationEvent(MyEvent event) {
System.out.println("MyEventListener :" + event.getValue());
}
}然后通过
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));
}
}使用@EventListener实现订阅者
在
package chkui.springcore.example.javabase.event.custom;
public class MyEventListenerAnnotation{
@EventListener
public void handleMyEvent(MyEvent event) {
System.out.println("MyEventListenerAnnotation :" + event.getValue());
}
}使用
我们也可以使用注解指定绑定的事件:
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());
}
}注意上面代码中的
注解,同一个事件可以被多个订阅者订阅。在多个定于者存在的情况下可以使用注解来指定他们的执行顺序,数值越小越优先执行。EL表达式设定事件监听的条件
通过注解还可以使用
事件:
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的方式来组合功能,而事件的订阅/发布仅仅用于辅助。