SpringBoot事件发布与订阅
在日常开发中,经常会遇到一个方法执行完毕,要通知另一个方法。 比如用户注册了之后需要给他发邮件。这种一个主要的业务,包含了很多附属的业务的情况, 如果对事务要求不是很严格,可以试试SpringBoot的事件发布与订阅。
事件类
首先你需要定义一个事件,这个类继承ApplicationEvent
这个抽象类。
import org.springframework.context.ApplicationEvent;
/**
* 定义一个自定义事件,继承ApplicationEvent类
*
* @author lww
* @date 2020-04-09 17:41
*/
public class MyApplicationEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
public MyApplicationEvent(Object source) {
super(source);
System.err.println("发布事件:source = " + source);
}
}
发布事件
发布事件很简单,注入 ApplicationContext
,调用 context.publishEvent(new MyApplicationEvent("发布事件啦"));
就好了,这样一个事件就发布出去了。
import com.ler.eventdemo.event.MyApplicationEvent;
import javax.annotation.Resource;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author lww
* @date 2020-04-09 18:05
*/
@RestController
public class HelloController {
@Resource
private ApplicationContext context;
@GetMapping("/hello")
public String hello(String name) {
context.publishEvent(new MyApplicationEvent("发布事件啦"));
return "Hello " + name;
}
}
在发布事件这里有一个小图标,点击就会跳到事件订阅的地方(在idea里)。
订阅事件
有了事件,又发布了事件,接下来就是订阅事件。也很简单,你只需要写一个listener
import com.ler.eventdemo.event.MyApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* 定义一个事件监听器 MyApplicationListener
*
* @author lww
* @date 2020-04-09 17:42
*/
@Component
public class MyApplicationListener {
@EventListener
public void onApplicationEvent(MyApplicationEvent event) {
System.err.println("接收到事件:" + event.getClass());
}
}
有的说要实现 ApplicationListener
接口,测试后发现,不需要实现什么接口。只要加@EventListener
注解,然后参数指定哪个事件,就可以了。
在订阅事件这里,也有一个小图标,点击会跳到发布事件那里(在idea里)。
@TransactionalEventListener
@TransactionalEventListener
和 @EventListener
差了一个 Transactional
,这个事务表示的意思是,
事件的发送时机可以和事务绑定。
- TransactionPhase.BEFORE_COMMIT 在提交前
- TransactionPhase.AFTER_COMMIT 在提交后
- TransactionPhase.AFTER_ROLLBACK 在回滚后
- TransactionPhase.AFTER_COMPLETION 在事务完成后
默认 TransactionPhase.AFTER_COMMIT
。
指定发布时机避免的情况就是,比如注册用户,包含了一些耗时的操作,而这些操作中有异步非阻塞的, 当执行到了发布事件的方法时。用户可能还没有创建完成,此时如果事件发布了,在监听器那边执行时,可能获取用户失败。 而如果在事务提交后执行,就不会出现这种情况。
这个注解 不是 说发布事件的方法和监听器响应方法之间有什么事务关系。他们之间还是没有事务的。无法保证原子性,一致性。
如果要实现事务也不是没有办法,可以先保证 事件的发布方执行完毕,事务提交完成。然后订阅方遵循幂等性规则, 如果订阅方失败,进入重试机制。有点像RocketMQ分段提交,事务回查与重试机制。可以按照这个思想实现。
原理
ApplicationContext
接口继承了ApplicationEventPublisher
接口,所以有publishEvent
方法,可以用于发布任务。ApplicationListener
接口继承了EventListener
接口,其中有一个onApplicationEvent
方法,用来监听事件。
在org.springframework.context.support.AbstractApplicationContext#refresh
方法中,
org.springframework.context.support.AbstractApplicationContext#registerListeners
里面

可以看到,注册监听器的时候是查找实现了ApplicationListener
的接口,那我们没有实现,又是如何注册的呢?

@EventListener
注释里有这一句,看这个类的这个方法
org.springframework.context.event.EventListenerMethodProcessor#processBean

在这里,查找使用了@EventListener
注解的方法,找到后同样会添加到ConfigurableApplicationContext
(ApplicationContext
的实现类)中,
作为listener。所以不实现ApplicationListener
,同样可以正常使用。
注意的是需要使用Java配置类,如果使用xml配置,则要添加 <context:annotation-config/>
或者 <context:component-scan/>
而 @TransactionalEventListener
包含@EventListener
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {
......
}
@TransactionalEventListener
由 org.springframework.transaction.event.TransactionalEventListenerFactory
来处理,
在这个方法中org.springframework.transaction.event.TransactionalEventListenerFactory#createApplicationListener
创建了org.springframework.transaction.event.ApplicationListenerMethodTransactionalAdapter#ApplicationListenerMethodTransactionalAdapter
在org.springframework.transaction.event.ApplicationListenerMethodTransactionalAdapter#onApplicationEvent
这个方法中,使用TransactionSynchronizationEventAdapter
来管理事务。
总结
SpringBoot 事件的发布订阅,使用还是非常简单方便的,在SpringBoot框架中也应用广泛,小伙伴们快快练起来吧。
本文使用 mdnice 排版
转载自:https://juejin.cn/post/6844904190590844936