目录
一、场景
二、Spring Event使用
????????????????2.1 定义事件
2.2?发布消息
2.1.1 通过ApplicationContext发布消息
2.1.2 通过ApplicationEventPublisher发布消息
2.3?订阅消息
2.2.1 通过实现ApplicationListener接口订阅消息
2.2.2 使用注解订阅消息
2.2.3 异步订阅消息
2.2.4 处理事物消息
一、场景
? ?我们生活中登陆一些APP的时候会收到短信和PUSH通知,在系统内部还会有一些记录登陆日志等处理,伪代码如下:
public boolean doLogin() {
log.info("login! send LoginEvent");
sendSms();
sendPush();
doLog();
return true;
}
可以发现通知逻辑和主业务逻辑耦合严重;如何解耦呢?下面我们看一下使用SpringEvent 进行解耦。?
二、Spring Event使用
Spring的应用上下文支持Bean之间基于事件的通信,发送者P OJO只需要发送事件即可,无须知道接收者是谁,可以存在多个事件接收者。使用步骤如下:
- 定义事件
- 发布事件
- 监听事件
2.1 定义事件
?在Spring 中定一个事件必须继承ApplicationEvent类
@ToString
@Getter
@Setter
public class LoginEvent extends ApplicationEvent {
Long userId;
public LoginEvent(Long userId, Object source) {
super(source);
this.userId = userId;
}
}
2.2?发布消息
? ? ?发布消息可以使用ApplicationContext或ApplicationEventPublisher 调用?publishEvent 方法进行发布消息
2.1.1 通过ApplicationContext发布消息
@Service
@Slf4j
public class LoginService implements ApplicationContextAware {
private ApplicationContext applicationContext;
public boolean doLogin() throws InterruptedException {
log.info("login");
applicationContext.publishEvent(new LoginEvent(1L, this));
return true;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
2.1.2 通过ApplicationEventPublisher发布消息
@Service
@Slf4j
public class LoginService {
@Autowired
private ApplicationEventPublisher publisher;
public boolean doLogin() throws InterruptedException {
log.info("login");
publisher.publishEvent(new LoginEvent(1L, this));
return true;
}
}
2.3?订阅消息
2.2.1 通过实现ApplicationListener接口订阅消息
@Component
@Slf4j
public class SmsNotifyService implements ApplicationListener<LoginEvent> {
@Override
public void onApplicationEvent(LoginEvent event) {
log.info("监听到登陆消息,发送端消息:{}", event);
}
}
2.2.2 使用注解订阅消息
@Service
@Slf4j
public class PushNotifyService {
@EventListener(LoginEvent.class)
public void sendPush(LoginEvent loginEvent) {
log.info("listener 接收到事件是:{}", loginEvent);
}
}
2.2.3 异步订阅消息
@Service
@Slf4j
public class LogService {
@Async
@EventListener(LoginEvent.class)
public void doLog(LoginEvent loginEvent) {
log.info(" userId:{} login", loginEvent.getUserId());
}
}
@EnableAsync
@SpringBootApplication
public class DistributionToolKitApplication {
public static void main(String[] args) {
SpringApplication.run(DistributionToolKitApplication.class, args);
}
}
?测试用例:
@SpringBootTest
@EnableAsync
class SpringEventTest {
@Autowired
private LoginService loginService;
@Test
void test() throws InterruptedException {
loginService.doLogin();
}
}
执行结果:?
2022-07-15 20:35:54.801 INFO 1351 --- [ main] c.k.d.toolkit.event.LoginService : login! send LoginEvent
2022-07-15 20:35:54.801 INFO 1351 --- [ main] c.k.d.toolkit.event.SmsNotifyService : 监听到登陆消息,发送端消息:LoginEvent(userId=1)
2022-07-15 20:35:54.805 INFO 1351 --- [ main] c.k.d.toolkit.event.PushNotifyService : listener 接收到事件是:LoginEvent(userId=1)
2022-07-15 20:35:54.816 INFO 1351 --- [ task-1] c.k.d.toolkit.event.LogService : userId:1 login
可以发现异步监听时发现这个的线程是task-1,不在主线程?
2022-07-15 20:35:54.816 ?INFO 1351 --- [ ? ? ? ? task-1] c.k.d.toolkit.event.LogService ? ? ? ? ? : ?userId:1 login
2.2.4 处理事物消息
在工作中有这样一个场景,数据更新后需要刷新缓存数据!如果使用上文的方式进行解耦,需要特别注意需要在事物提交以后发送事件消息;如果是在事物范围内发送事件消息则会存在读取数据时候不是最新数据,因为此时事物还可能没有提交。那么如何解决这个问题呢?在Spring中也为我们考虑了这样的场景.可以使用 @TransactionEventListener注解代替 @EventListener
@Service
@Slf4j
public class RefreshService {
@TransactionalEventListener(LoginEvent.class)
@Async
public void sendPush(LoginEvent loginEvent) {
log.info("RefreshService 接收到事件是:{}", loginEvent);
}
}
|