事件可以实现组件之间的解耦,这也是观察者设计模式
从需求开始
一个简单的场景,就是用户注册成功后,发短信通知和发邮件通知,其实这样的场景就是两种处理情况

  1. 注册成功后起一个线程执行发短信和发邮件通知
  2. 发送用户ID到mq,然后mq去执行

    1. @Component
    2. @Slf4j
    3. public class MyService {
    4. public void execute(){
    5. log.info("主线业务");
    6. // 主线业务完成后需要做些支线业务,下面是问题代码
    7. log.info("发送短信");
    8. log.info("发送邮件");
    9. }
    10. }

    这里怎么让主线任务和支线任务互不干扰,低耦合,互补影响?
    那就把主线这边作为事件的发布者,支线这边作为事件的接收者(监听器),通过事件把他们联系起来
    那就需要一个事件对象,Spring中的事件对象可以定义一个类继承ApplicationEvent

    1. // 因为 ApplicationEvent 没有无参构造,所以要实现一个有参构造
    2. public class MyEvent extends ApplicationEvent {
    3. // 构造参数代表一个事件源,谁发的事件
    4. public MyEvent(Object source) {
    5. super(source);
    6. }
    7. }

    事件对象有了,接下来就是把支线的任务分离出去,写在监听器代码里,主线只管发事件,监听器去处理业务

    1. @Component
    2. @Slf4j
    3. public class MyService {
    4. // 实际上就是ApplicationContext,因为ApplicationContext实现了ApplicationEventPublisher
    5. @Autowired
    6. private ApplicationEventPublisher publisher;
    7. public void execute() {
    8. log.info("主线业务");
    9. // 主线业务完成后需要做些直线业务,下面是问题代码
    10. // log.info("发送短信");
    11. // log.info("发送邮件");
    12. publisher.publishEvent(new MyEvent("MyService.execute()发送的事件"));
    13. }
    14. }

    发事件_推荐用法```java @Component @Slf4j public class MyService { public void execute() {

    1. log.info("主线业务");
    2. ApplicationContextHelper.getApplicationContext().publishEvent(new MyEvent());
    3. 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;

  1. @Override
  2. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  3. ApplicationContextHelper.applicationContext = applicationContext;
  4. }
  5. public static ApplicationContext getApplicationContext(){
  6. return ApplicationContextHelper.applicationContext;
  7. }
  8. public static<T> T getBean(Class<T> targetClz){
  9. T beanInstance = null;
  10. //优先按type查
  11. try {
  12. beanInstance = (T) applicationContext.getBean(targetClz);
  13. }catch (Exception e){
  14. }
  15. //按name查
  16. if(beanInstance == null){
  17. String simpleName = targetClz.getSimpleName();
  18. //首字母小写
  19. simpleName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
  20. beanInstance = (T) applicationContext.getBean(simpleName);
  21. }
  22. return beanInstance;
  23. }
  24. public static Object getBean(String claz){
  25. return ApplicationContextHelper.applicationContext.getBean(claz);
  26. }

}

  1. <a name="Kahbr"></a>
  2. # 1. 实现ApplicationListener接口
  3. ```java
  4. @Component
  5. @Slf4j
  6. // ApplicationListener<> 是个泛型,可以做一个筛选,只监听MyEvent的事件,其他的不会处理
  7. public class SmsApplicationListener implements ApplicationListener<MyEvent> {
  8. // 发布事件以后,随机就会触发onApplicationEvent
  9. @Override
  10. public void onApplicationEvent(MyEvent event) {
  11. // 在这里执行支线任务
  12. log.info("发送短信");
  13. }
  14. }
  1. @Component
  2. @Slf4j
  3. public class EmailApplicationListener implements ApplicationListener<MyEvent> {
  4. @Override
  5. public void onApplicationEvent(MyEvent event) {
  6. log.info("发送邮件");
  7. }
  8. }

可以看到主线业务是MyService执行的,发邮件和发短信分别是监听器执行的
image.png

2. 通过@EventListener注解

  1. @Component
  2. @Slf4j
  3. public class EmailService {
  4. // 自定方法,返回值,方法名,可随意,方法参数有要求,参数是接收事件对象的
  5. // 监听器在事件发生时就会被调用,然后把事件对象作为方法参数传递进来
  6. @EventListener
  7. public void listener(MyEvent myEvent){
  8. log.info("发送邮件-》注解的方式实现监听");
  9. }
  10. }

image.png

3. 扩展-> 异步执行

3.1 线程池异步-异步广播事件

  1. @Configuration
  2. public class ThreadPoolConfig {
  3. @Bean
  4. public ThreadPoolExecutor executor() {
  5. return new ThreadPoolExecutor(
  6. 5,
  7. 20,
  8. 3,
  9. TimeUnit.SECONDS,
  10. new ArrayBlockingQueue<>(1000),
  11. Executors.defaultThreadFactory(),
  12. new ThreadPoolExecutor.CallerRunsPolicy()
  13. );
  14. }
  15. }

ApplicationEventPublisher底层是调用了SimpleApplicationEventMulticaster事件广播器对象,它是真正发的事件的,它发事件的时候就可以设置是否利用线程池发送事件, 默认情况下的单线程的,如果提供了一个ApplicationEventPublisher并且配置了线程池,那么就会就可以线程池异步发送事件
image.png
它实现了一个接口,可以用它接口,也可以直接用它的实现
image.png

  1. @Component
  2. @Slf4j
  3. public class MyService {
  4. // 实际上就是ApplicationContext,因为ApplicationContext实现了ApplicationEventPublisher
  5. @Autowired
  6. private ApplicationEventPublisher publisher;
  7. @Autowired
  8. ThreadPoolConfig poolConfig;
  9. public void execute() {
  10. log.info("主线业务");
  11. // 主线业务完成后需要做些直线业务,下面是问题代码
  12. // log.info("发送短信");
  13. // log.info("发送邮件");
  14. publisher.publishEvent(new MyEvent("MyService.execute()发送的事件"));
  15. }
  16. // 方法名必须为 applicationEventMulticaster 才会覆盖默认的
  17. @Bean
  18. public SimpleApplicationEventMulticaster applicationEventMulticaster(){
  19. SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
  20. multicaster.setTaskExecutor(poolConfig.executor());
  21. return multicaster;
  22. }
  23. }

image.png

3.2 异步注解-异步监听事件

启动类上开始异步@EnableAsync

  1. @Component
  2. @Slf4j
  3. public class EmailService {
  4. // 在监听事件上开始异步s
  5. // Async异步注解,executor为自定义线程池Bean的名字
  6. @EventListener
  7. @Async("executor")
  8. public void listener(MyEvent myEvent) {
  9. log.info("发送邮件-》注解的方式实现监听");
  10. }
  11. }

image.png

3. @EventListener原理