事件可以实现组件之间的解耦,这也是观察者设计模式
从需求开始
一个简单的场景,就是用户注册成功后,发短信通知和发邮件通知,其实这样的场景就是两种处理情况
- 注册成功后起一个线程执行发短信和发邮件通知
发送用户ID到mq,然后mq去执行
@Component@Slf4jpublic 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@Slf4jpublic class MyService {// 实际上就是ApplicationContext,因为ApplicationContext实现了ApplicationEventPublisher@Autowiredprivate 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("事件发送完毕");
} }
java import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component;
@Component public class ApplicationContextHelper implements ApplicationContextAware { private static ApplicationContext applicationContext;
@Overridepublic 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@Overridepublic void onApplicationEvent(MyEvent event) {// 在这里执行支线任务log.info("发送短信");}}
@Component@Slf4jpublic class EmailApplicationListener implements ApplicationListener<MyEvent> {@Overridepublic void onApplicationEvent(MyEvent event) {log.info("发送邮件");}}
可以看到主线业务是MyService执行的,发邮件和发短信分别是监听器执行的
2. 通过@EventListener注解
@Component@Slf4jpublic class EmailService {// 自定方法,返回值,方法名,可随意,方法参数有要求,参数是接收事件对象的// 监听器在事件发生时就会被调用,然后把事件对象作为方法参数传递进来@EventListenerpublic void listener(MyEvent myEvent){log.info("发送邮件-》注解的方式实现监听");}}
3. 扩展-> 异步执行
3.1 线程池异步-异步广播事件
@Configurationpublic class ThreadPoolConfig {@Beanpublic ThreadPoolExecutor executor() {return new ThreadPoolExecutor(5,20,3,TimeUnit.SECONDS,new ArrayBlockingQueue<>(1000),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());}}
ApplicationEventPublisher底层是调用了SimpleApplicationEventMulticaster事件广播器对象,它是真正发的事件的,它发事件的时候就可以设置是否利用线程池发送事件, 默认情况下的单线程的,如果提供了一个ApplicationEventPublisher并且配置了线程池,那么就会就可以线程池异步发送事件
它实现了一个接口,可以用它接口,也可以直接用它的实现
@Component@Slf4jpublic class MyService {// 实际上就是ApplicationContext,因为ApplicationContext实现了ApplicationEventPublisher@Autowiredprivate ApplicationEventPublisher publisher;@AutowiredThreadPoolConfig poolConfig;public void execute() {log.info("主线业务");// 主线业务完成后需要做些直线业务,下面是问题代码// log.info("发送短信");// log.info("发送邮件");publisher.publishEvent(new MyEvent("MyService.execute()发送的事件"));}// 方法名必须为 applicationEventMulticaster 才会覆盖默认的@Beanpublic SimpleApplicationEventMulticaster applicationEventMulticaster(){SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();multicaster.setTaskExecutor(poolConfig.executor());return multicaster;}}
3.2 异步注解-异步监听事件
启动类上开始异步@EnableAsync
@Component@Slf4jpublic class EmailService {// 在监听事件上开始异步s// Async异步注解,executor为自定义线程池Bean的名字@EventListener@Async("executor")public void listener(MyEvent myEvent) {log.info("发送邮件-》注解的方式实现监听");}}
