研磨Spring事件机制
本文就如何使用Spring事件机制进行较为详细的总结。
Spring事件概述
- 每一个Spring事件都需要继承ApplicationEvent的类,ApplicationEvent又继承自java.util.EventObject
- Spring中的任何bean都能够通过实现ApplicationListener 接口来监听事件;这里的泛型T就是上面提到的继承了ApplicationEvent的类
- SpringContext会对实现ApplicationListener接口的任意bean进行自动注册
- 发布事件是通过ApplicationEventPublisher.publishEvent方法实现的。应用中一般通过ApplicationContext进行事件发布,ApplicationContext实现了ApplicationEventPublisher接口
- 另一种发布事件的方式是bean实现ApplicationContextAware接口,拿到ApplicationContext实例从而实现事件发布
Spring事件机制实战
我们通过一个简单的案例对Spring事件机制使用进行总结。
定义事件
首先需要对事件进行定义,事件可以理解为消息队列中的消息,即:当满足某个条件时候需要发布的一个实体,该实体中包含了我们需要事件监听器处理的内容。
比如:当数据库中的内容发生更新,我们需要同步对缓存中的数据进行更新,那么我们就可以在数据库更新成功之后发布一个缓存更新事件,将更新后的数据实体通过publishEvent发布出去,
在另一个监听器类中获取到该缓存更新事件对缓存进行更新即可。这是一个事件机制的典型应用。
一个事件是一个javabean,它需要继承ApplicationEvent。
public class DemoEvent extends ApplicationEvent { private String key; private String value; public DemoEvent(Object source) { super(source); } public String getKey() { return key; } public DemoEvent setKey(String key) { this.key = key; return this; } public String getValue() { return value; } public DemoEvent setValue(String value) { this.value = value; return this; } @Override public String toString() { return "DemoEvent{" + "key='" + key + '\'' + ", value='" + value + '\'' + '}'; } }
上述这个类继承了ApplicationEvent,key、value是它的属性,在实际应用中,属性可以使任意的应用数据。
需要注意的是,ApplicationEvent具有一个接受指向事件源的引用的构造函数,在DemoEvent的构造函数中,需要通过super(source);传入事件源的引用。
定义事件监听器
ApplicationListener接口定义了方法onApplicationEvent,当触发了一个事件的时候,Spring框架会回调onApplicationEvent方法。
通过实现ApplicationListener接口,应用事件监听器需要对接收到的事件类进行处理。
@Component public class DemoEventListener implements ApplicationListener { private static final Map CONTAINER = new ConcurrentHashMap(); @Override public void onApplicationEvent(DemoEvent demoEvent) { CONTAINER.put(demoEvent.getKey(), demoEvent.getValue()); System.out.println("[DemoEventListener]接受事件--" + demoEvent.toString()); System.out.println(CONTAINER.toString()); } }
这里的逻辑是DemoEventListener实现了强类型的ApplicationListener接口,对我们发布的DemoEvent事件进行处理。
具体的处理逻辑为将DemoEvent的key、value属性添加到CONTAINER这个Map中,模拟缓存更新。
另外一种创建事件监听器的方式为可以使用注解 @EventListener
:原理就是通过扫描这个注解,创建监听器并添加到ApplicationContext
发布事件
通过接口ApplicationEventPublisher的publishEvent方法我们能够完成发布事件的目的,在Spring框架中AbstractApplicationContext实现了ApplicationEventPublisher接口。
因此我们能够在应用中通过获取ApplicationContext实例使用事件发布能力。
也可以通过实现ApplicationEventPublisherAware接口来发布事件
@Component public class DemoEventPublisher implements CommandLineRunner { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0); @Autowired ApplicationContext context; @Override public void run(String... args) throws Exception { executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { // 设置事件源 DemoEvent demoEvent = new DemoEvent(this); demoEvent.setKey(String.valueOf(ATOMIC_INTEGER.getAndAdd(1))).setValue("snowalker"); context.publishEvent(demoEvent); System.out.println("[DemoEventPublisher]发布事件:" + demoEvent.toString()); } }, 0, 3000, TimeUnit.MILLISECONDS); } }
我们通过context.publishEvent(demoEvent);发布了缓存更新事件,每3秒发布一次。另一种写法为
@Component public class DemoEventPublisher2 implements CommandLineRunner, ApplicationEventPublisherAware { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0); ApplicationEventPublisher applicationEventPublisher; @Override public void run(String... args) throws Exception { executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { // 设置事件源 DemoEvent demoEvent = new DemoEvent(this); demoEvent.setKey(String.valueOf(ATOMIC_INTEGER.getAndAdd(1))).setValue("snowalker"); System.out.println("[DemoEventPublisher]发布事件:" + demoEvent.toString()); applicationEventPublisher.publishEvent(demoEvent); } }, 0, 3000, TimeUnit.MILLISECONDS); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } }
这两种写法效果一致,最终都是通过applicationEventPublisher进行了事件的发布。
案例运行
[DemoEventPublisher]发布事件:DemoEvent{key='0', value='snowalker'} [DemoEventListener]接受事件--DemoEvent{key='0', value='snowalker'} {0=snowalker} [DemoEventPublisher]发布事件:DemoEvent{key='1', value='snowalker'} [DemoEventListener]接受事件--DemoEvent{key='1', value='snowalker'} {0=snowalker, 1=snowalker} [DemoEventPublisher]发布事件:DemoEvent{key='2', value='snowalker'} [DemoEventListener]接受事件--DemoEvent{key='2', value='snowalker'} {0=snowalker, 1=snowalker, 2=snowalker} ......
可以看到,通过事件机制,我们实现了进程内的事件通信,这种方式能够很好的对业务进行解耦合,更加灵活的进行业务处理。
事务绑定事件
另外需要注意的是,某些场景下,我们需要在事务提交之后再发布事件,这里就涉及到了事务绑定事件能力。
具体的方式为使用 @TransactionalEventListener注解
或者 TransactionSynchronizationManager
类来解决此类问题,也就是:事务成功提交之后,再发布事件。
当然我们也可以在事件提交之后将结果返回给调用方然后发布事件,但是这种方式不够优雅,因此还是建议使用事务绑定事件的方式进行基于事务的事件发布。
异步事件支持
上述的方式为同步事件,我们可以通过配置开启异步事件支持。
通过使用@Async注解事件监听器开启异步支持,需要要开启对异步注解的支持.
- java配置通过@EnableAsync开启.
- 如果是xml配置文件则需要配置:
版权声明:
原创不易,洗文可耻。除非注明,本博文章均为原创,转载请以链接形式标明本文地址。