Spring事件驱动模型,简单来说类似于Message-Queue消息队列中的Pub/Sub发布/订阅模式,也类似于Java设计模式中的观察者模式。
自定义事件 Spring的事件接口位于org.springframework.context.ApplicationEvent
,源码如下:
1 2 3 4 5 6 7 8 9 10 11 public abstract class ApplicationEvent extends EventObject { private static final long serialVersionUID = 7099057708183571937L ; private final long timestamp; public ApplicationEvent (Object source) { super (source); this .timestamp = System.currentTimeMillis(); } public final long getTimestamp () { return this .timestamp; } }
继承了Java的事件对象EventObject
,所以可以使用getSource()
方法来获取到事件传播对象。
自定义Spring事件 1 2 3 4 5 6 7 8 9 10 11 12 public class CustomSpringEvent extends ApplicationEvent { private String message; public CustomSpringEvent (Object source, String message) { super (source); this .message = message; } public String getMessage () { return message; } }
然后定义事件监听器,该监听器实际上等同于消费者,需要交给Spring容器管理。
1 2 3 4 5 6 7 @Component public class CustomSpringEventListener implements ApplicationListener <CustomSpringEvent > { @Override public void onApplicationEvent (CustomSpringEvent event) { System.out.println("Received spring custom event - " + event.getMessage()); } }
最后定义事件发布者
1 2 3 4 5 6 7 8 9 10 11 @Component public class CustomSpringEventPublisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; public void doStuffAndPublishAnEvent (final String message) { System.out.println("Publishing custom event. " ); CustomSpringEvent customSpringEvent = new CustomSpringEvent(this , message); applicationEventPublisher.publishEvent(customSpringEvent); } }
创建测试类
1 2 3 4 5 6 7 8 9 10 11 12 @RunWith(SpringRunner.class) @SpringBootTest public class CustomSpringEventPublisherTest { @Autowired private CustomSpringEventPublisher publisher; @Test public void publishStringEventTest () { publisher.doStuffAndPublishAnEvent("111" ); } }
运行测试类,可以看到控制台打印了两条重要信息
1 2 3 4 //发布事件 Publishing custom event. //监听器得到了事件,并相应处理 Received spring custom event - 111
由于Spring事件是发布/订阅的模式,而发布订阅模式有以下三种情况
1生产者 - 1消费者
1生产者 - 多消费者
多生产者 - 多消费者
上面举的例子是第一种情况,我们来试试其他两个情况
继续创建一个事件监听器作为消费者:
1 2 3 4 5 6 7 @Component public class CustomSpringEventListener2 implements ApplicationListener <CustomSpringEvent > { @Override public void onApplicationEvent (CustomSpringEvent event) { System.out.println("CustomSpringEventListener2 Received spring custom event - " + event.getMessage()); } }
运行测试类后,可以观察到,控制台顺序打印了两条消费信息:
1 2 3 Publishing custom event. CustomSpringEventListener1 Received spring custom event - 111 CustomSpringEventListener2 Received spring custom event - 111
说明,Spring的发布订阅模式是广播模式,所有消费者都能接受到消息,并正常消费
再试试第三种多生产者 - 多消费者的情况
继续创建一个发布者,
1 2 3 4 5 6 7 8 9 10 11 @Component public class CustomSpringEventPublisher2 { @Autowired private ApplicationEventPublisher applicationEventPublisher; public void doStuffAndPublishAnEvent (final String message) { System.out.println("CustomSpringEventPublisher2 Publishing custom event. " ); CustomSpringEvent customSpringEvent = new CustomSpringEvent(this , message); applicationEventPublisher.publishEvent(customSpringEvent); } }
控制台输出:
1 2 3 4 5 6 CustomSpringEventPublisher Publishing custom event. CustomSpringEventListener1 Received spring custom event - 111 CustomSpringEventListener2 Received spring custom event - 111 CustomSpringEventPublisher2 Publishing custom event. CustomSpringEventListener1 Received spring custom event - 222 CustomSpringEventListener2 Received spring custom event - 222
从以上输出内容,我们可以猜测到,Spring的事件发布订阅机制是同步进行的,也就是说,事件必须被所有消费者消费完成之后,发布者的代码才能继续往下走,这显然不是我们想要的效果,那有没有异步执行的事件呢?
Spring中的异步事件 要使用Spring 的异步事件,我们需要自定义异步事件配置类
1 2 3 4 5 6 7 8 9 10 11 @Configuration public class AsynchronousSpringEventsConfig { @Bean(name = "applicationEventMulticaster") public ApplicationEventMulticaster simpleApplicationEventMulticaster () { SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); return eventMulticaster; } }
发布和订阅的代码不用变动,直接运行测试类,控制台将打印出:
1 2 3 4 5 6 CustomSpringEventPublisher Publishing custom event. CustomSpringEventPublisher2 Publishing custom event. CustomSpringEventListener1 Received spring custom event - 111 CustomSpringEventListener2 Received spring custom event - 111 CustomSpringEventListener2 Received spring custom event - 222 CustomSpringEventListener1 Received spring custom event - 222
可以看到,两个发布者几乎同时运行,证明监听器是异步执行的,没有阻塞住发布者的代码。准确的说,监听器将在一个单独的线程中异步处理事件。
Spring自带的事件类型 事件驱动在Spring中是被广泛采用的,我们查看ApplicationEvent的子类可以发现许多Event事件,在此不赘述。
注解驱动的监听器 从Spring 4.2开始,事件监听器不需要是实现ApplicationListener
接口的bean,它可以通过@EventListener
注解在任何被Spring容器管理的bean的公共方法上。
1 2 3 4 5 6 7 @Component public class AnnotationDrivenContextStartedListener { @EventListener public void handleContextStart (CustomSpringEvent cse) { System.out.println("Handling Custom Spring Event." ); } }
控制台输出结果:
1 2 3 4 CustomSpringEventPublisher Publishing custom event. Handling Custom Spring Event. CustomSpringEventPublisher2 Publishing custom event. Handling Custom Spring Event.
同样的,我们可以看出,这个事件监听器是同步执行的,如果要改为异步监听器,在事件方法上加上@Async
,并且在Spring应用中开启异步支持(在SpringBootApplication
上添加@EnableAsync
)。
1 2 3 4 5 6 7 8 @Component public class AnnotationDrivenContextStartedListener { @Async @EventListener public void handleContextStart (CustomSpringEvent cse) { System.out.println("Handling Custom Spring Event." ); } }
再次运行测试类:
1 2 3 4 CustomSpringEventPublisher Publishing custom event. CustomSpringEventPublisher2 Publishing custom event. Handling Custom Spring Event. Handling Custom Spring Event.
泛型支持 创建一个通用泛型事件模型
1 2 3 4 5 6 7 8 9 10 @Data public class GenericSpringEvent <T > { private T message; protected boolean success; public GenericSpringEvent (T what, boolean success) { this .message = what; this .success = success; } }
注意GenericSpringEvent
和CustomSpringEvent
之间的区别。我们现在可以灵活地发布任何任意事件,并且不再需要从ApplicationEvent
扩展。
这样的话,我们无法像之前一样,通过继承ApplicationListener
的方式来定义一个监听器,因为ApplicationListener
定义了事件必须是ApplicationEvent
的子类。所以,我们只能使用注解驱动的监听器。
通过在@EventListener
注释上定义布尔SpEL表达式,也可以使事件监听器成为条件。在这种情况下,只会为成功的String的GenericSpringEvent
调用事件处理程序:
1 2 3 4 5 6 7 @Component public class AnnotationDrivenEventListener { @EventListener(condition = "#event.success") public void handleSuccessful (GenericSpringEvent<String> event) { System.out.println("Handling generic event (conditional)." ); } }
定义具体类型的事件:
1 2 3 4 5 public class StringGenericSpringEvent extends GenericSpringEvent <String > { public StringGenericSpringEvent (String message, boolean success) { super (message, success); } }
定义发布者:
1 2 3 4 5 6 7 8 9 10 11 @Component public class StringGenericSpringEventPublisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; public void doStuffAndPublishAnEvent (final String message, final boolean success) { System.out.println("CustomSpringEventPublisher Publishing custom event. " ); StringGenericSpringEvent springEvent = new StringGenericSpringEvent(message, success); applicationEventPublisher.publishEvent(springEvent); } }
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 @RunWith(SpringRunner.class) @SpringBootTest public class CustomSpringEventPublisherTest { @Autowired private StringGenericSpringEventPublisher publisher; @Test public void publishStringEventTest () { publisher.doStuffAndPublishAnEvent("success" , true ); publisher.doStuffAndPublishAnEvent("failed" , false ); } }
运行结果:
1 2 3 CustomSpringEventPublisher Publishing custom event. Handling generic event (conditional) success CustomSpringEventPublisher Publishing custom event.
监听器只处理了成功的事件,成功忽略掉了失败的事件。这样的好处是,可以为同一个事件定义成功和失败不同的操作。
Spring事件的事务绑定 从Spring 4.2开始,框架提供了一个新的@TransactionalEventListener
注解,它是@EventListener
的扩展,允许将事件的侦听器绑定到事务的一个阶段。绑定可以进行以下事务阶段:
AFTER_COMMIT(默认的):在事务成功后触发
AFTER_ROLLBACK:事务回滚时触发
AFTER_COMPLETION:事务完成后触发,不论是否成功
BEFORE_COMMIT:事务提交之前触发
总结
Spring中处理事件的基础知识:创建一个简单的自定义事件,发布它,然后在监听器中处理它。
在配置中启用事件的异步处理。
Spring 4.2中引入的改进,例如注释驱动的侦听器,更好的泛型支持以及绑定到事务阶段的事件。
👉 本文代码地址