事件可以实现组件之间的解耦,这也是观察者设计模式
从需求开始
一个简单的场景,就是用户注册成功后,发短信通知和发邮件通知,其实这样的场景就是两种处理情况
- 注册成功后起一个线程执行发短信和发邮件通知
发送用户ID到mq,然后mq去执行
@Component
@Slf4j
public class MyService {
public void execute(){
log.info("主线业务");
// 主线业务完成后需要做些支线业务,下面是问题代码
log.info("发送短信");
log.info("发送邮件");
}
}
这里怎么让主线任务和支线任务互不干扰,低耦合,互补影响?
那就把主线这边作为事件的发布者,支线这边作为事件的接收者(监听器),通过事件把他们联系起来
那就需要一个事件对象,Spring中的事件对象可以定义一个类继承ApplicationEvent
// 因为 ApplicationEvent 没有无参构造,所以要实现一个有参构造
public class MyEvent extends ApplicationEvent {
// 构造参数代表一个事件源,谁发的事件
public MyEvent(Object source) {
super(source);
}
}
事件对象有了,接下来就是把支线的任务分离出去,写在监听器代码里,主线只管发事件,监听器去处理业务
@Component
@Slf4j
public class MyService {
// 实际上就是ApplicationContext,因为ApplicationContext实现了ApplicationEventPublisher
@Autowired
private ApplicationEventPublisher publisher;
public void execute() {
log.info("主线业务");
// 主线业务完成后需要做些直线业务,下面是问题代码
// log.info("发送短信");
// log.info("发送邮件");
publisher.publishEvent(new MyEvent("MyService.execute()发送的事件"));
}
}
发事件_推荐用法```java @Component @Slf4j public class MyService { public void execute() {
log.info("主线业务");
ApplicationContextHelper.getApplicationContext().publishEvent(new MyEvent());
log.info("事件发送完毕");
} }
@Component public class ApplicationContextHelper implements ApplicationContextAware { private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHelper.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext(){
return ApplicationContextHelper.applicationContext;
}
public static<T> T getBean(Class<T> targetClz){
T beanInstance = null;
//优先按type查
try {
beanInstance = (T) applicationContext.getBean(targetClz);
}catch (Exception e){
}
//按name查
if(beanInstance == null){
String simpleName = targetClz.getSimpleName();
//首字母小写
simpleName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
beanInstance = (T) applicationContext.getBean(simpleName);
}
return beanInstance;
}
public static Object getBean(String claz){
return ApplicationContextHelper.applicationContext.getBean(claz);
}
}
<a name="Kahbr"></a>
# 1. 实现ApplicationListener接口
```java
@Component
@Slf4j
// ApplicationListener<> 是个泛型,可以做一个筛选,只监听MyEvent的事件,其他的不会处理
public class SmsApplicationListener implements ApplicationListener<MyEvent> {
// 发布事件以后,随机就会触发onApplicationEvent
@Override
public void onApplicationEvent(MyEvent event) {
// 在这里执行支线任务
log.info("发送短信");
}
}
@Component
@Slf4j
public class EmailApplicationListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.info("发送邮件");
}
}
可以看到主线业务是MyService执行的,发邮件和发短信分别是监听器执行的
2. 通过@EventListener注解
@Component
@Slf4j
public class EmailService {
// 自定方法,返回值,方法名,可随意,方法参数有要求,参数是接收事件对象的
// 监听器在事件发生时就会被调用,然后把事件对象作为方法参数传递进来
@EventListener
public void listener(MyEvent myEvent){
log.info("发送邮件-》注解的方式实现监听");
}
}
3. 扩展-> 异步执行
3.1 线程池异步-异步广播事件
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolExecutor executor() {
return new ThreadPoolExecutor(
5,
20,
3,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
ApplicationEventPublisher
底层是调用了SimpleApplicationEventMulticaster
事件广播器对象,它是真正发的事件的,它发事件的时候就可以设置是否利用线程池发送事件, 默认情况下的单线程的,如果提供了一个ApplicationEventPublisher
并且配置了线程池,那么就会就可以线程池异步发送事件
它实现了一个接口,可以用它接口,也可以直接用它的实现
@Component
@Slf4j
public class MyService {
// 实际上就是ApplicationContext,因为ApplicationContext实现了ApplicationEventPublisher
@Autowired
private ApplicationEventPublisher publisher;
@Autowired
ThreadPoolConfig poolConfig;
public void execute() {
log.info("主线业务");
// 主线业务完成后需要做些直线业务,下面是问题代码
// log.info("发送短信");
// log.info("发送邮件");
publisher.publishEvent(new MyEvent("MyService.execute()发送的事件"));
}
// 方法名必须为 applicationEventMulticaster 才会覆盖默认的
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster(){
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(poolConfig.executor());
return multicaster;
}
}
3.2 异步注解-异步监听事件
启动类上开始异步@EnableAsync
@Component
@Slf4j
public class EmailService {
// 在监听事件上开始异步s
// Async异步注解,executor为自定义线程池Bean的名字
@EventListener
@Async("executor")
public void listener(MyEvent myEvent) {
log.info("发送邮件-》注解的方式实现监听");
}
}