springboot监听器的使用(ApplicationListener、SmartApplicationListener、@EventListener)
作者:mmseoamin日期:2024-01-18

目录

  • 前言
  • 1. ApplicationListener
    • 1. 简单的全局监听
    • 2. 定时任务
    • 3. 监听自定义事件
    • 2. SmartApplicationListener
      • 1. 简单使用
      • 2. 方法介绍
      • 3. @EventListener

        前言

        监听器: 当某个事件触发的时候,就会执行的方法块。

        springboot提供了两个接口来实现监听:ApplicationListenerSmartApplicationListener,如下图。显而易见,SmartApplicationListenerApplicationListener 的子类,故而其功能要强于 ApplicationListener

        springboot监听器的使用(ApplicationListener、SmartApplicationListener、@EventListener),第1张

        springboot监听器的使用(ApplicationListener、SmartApplicationListener、@EventListener),第2张

        当然,springboot很贴心地提供了一个 @EventListener 注解来实现监听。

        1. ApplicationListener

        1. 简单的全局监听

        首先,先来简单体验一下监听器的功能。

        需求: 在spring容器初始化完成之后就开始监听,并打印日志。

        实现:

        • 准备springboot工程(依赖:springboot、lombok)

        • 写一个监听器

          @Slf4j
          @Component
          public class MyTask implements ApplicationListener {
              private static boolean aFlag = false;
              @Override
              public void onApplicationEvent(ContextRefreshedEvent event) {
                  if (!aFlag) {
                      aFlag = true;
                      log.info("我已经监听到了");
                  }
              }
          }
          
        • 启动项目,控制台输出如下:

          springboot监听器的使用(ApplicationListener、SmartApplicationListener、@EventListener),第3张

          现在来说一下为什么要这么写监听器:

          • 实现接口

            implements ApplicationListener
            

            自定义监听器需要实现 ApplicationListener 接口

            ContextRefreshedEvent 是一个事件,它会在 spring容器初始化完成 之后被触发,所以监听器就会在 spring容器初始化完成之后开始监听,所以这就是所谓的全局监听

          • 标志位 aFlag

            private static boolean aFlag = false;
            

            aFlag 是一个启动标志

            因为web应用会出现父子容器,这样就会触发两次监听任务,所以需要一个标志位,保证监听任务(log.info(“我已经监听到了”))只会触发一次

            2. 定时任务

            需求: 实现一个定时任务,每间隔5秒、10秒、15秒、20秒、25秒、30秒、40秒、50秒、60秒,在控制台打印一次日志。

            实现: 可通过多线程的方式实现

            • 新建一个类 TimerRunner 继承 Runnable

            • 5秒到60秒的间隔可以通过 枚举类实现

              private enum TimerEnum {
                  // 第5秒打印
                  FIRST(1, 5 ),
                  // 第10秒打印
                  SECOND(2, 10),
                  // 第15秒打印
                  THIRD(3, 15),
                  // 第20秒打印
                  FOURTH(4, 20),
                  // 第25秒打印
                  FIFTH(5, 25),
                  // 第30秒打印
                  SIXTH(6, 30),
                  // 第40秒打印
                  SEVENTH(7, 40),
                  // 第50秒打印
                  EIGHTH(8, 50),
                  // 第60秒打印
                  NINTH(9, 60);
                  private Integer count;
                  private Integer time;
                  TimerEnum(Integer count, Integer time) {
                      this.count = count;
                      this.time = time;
                  }
                  public Integer getCount() {
                      return count;
                  }
                  public Integer getTime() {
                      return time;
                  }
              }
              
            • TimeRunner 完整代码

              @Slf4j
              public class TimerRunner implements Runnable{
                  @Override
                  public void run() {
                      // 打印次数
                      int count = 1;
                      SimpleDateFormat dateFormat= new SimpleDateFormat("hh:mm:ss");
                      for (TimerEnum item: TimerEnum.values()) {
                          if (count == item.getCount()) {
                              if (count != 9) {
                                  log.info("时间: " + dateFormat.format(new Date()) + "第 " + count + " 次打印,还剩余 " + (9 - count) + " 次完成打印");
                                  count++;
                              } else {
                                  log.info("最后一次打印");
                                  log.info("已完成所有打印任务!");
                              }
                          }
                          try {
                          	// TimeUnit来sleep,可读性更好
                              TimeUnit.SECONDS.sleep(item.getTime());
                          } catch (InterruptedException e) {
                              throw new RuntimeException(e);
                          }
                      }
                  }
                  private enum TimerEnum {
                      // 第5秒打印
                      FIRST(1, 5 ),
                      // 第10秒打印
                      SECOND(2, 10),
                      // 第15秒打印
                      THIRD(3, 15),
                      // 第20秒打印
                      FOURTH(4, 20),
                      // 第25秒打印
                      FIFTH(5, 25),
                      // 第30秒打印
                      SIXTH(6, 30),
                      // 第40秒打印
                      SEVENTH(7, 40),
                      // 第50秒打印
                      EIGHTH(8, 50),
                      // 第60秒打印
                      NINTH(9, 60);
                      private Integer count;
                      private Integer time;
                      TimerEnum(Integer count, Integer time) {
                          this.count = count;
                          this.time = time;
                      }
                      public Integer getCount() {
                          return count;
                      }
                      public Integer getTime() {
                          return time;
                      }
                  }
              }
              
            • MyTask 代码

              @Slf4j
              @Component
              public class MyTask implements ApplicationListener {
                  private static boolean aFlag = false;
                  @Override
                  public void onApplicationEvent(ContextRefreshedEvent event) {
                      if (!aFlag) {
                          aFlag = true;
                          new Thread(new TimerRunner()).start();
                      }
                  }
              }
              
            • 控制台输出:

              springboot监听器的使用(ApplicationListener、SmartApplicationListener、@EventListener),第4张

              3. 监听自定义事件

              Spring的 ApplicationContext 提供了支持事件和代码中监听器的功能。

              我们可以创建bean用来监听在 ApplicationContext 中发布的事件。ApplicationEvent 类在 ApplicationContext 接口中处理的事件,如果一个bean实现了 ApplicationListener 接口,当一个 ApplicationEvent 被发布以后,bean会自动被通知。


              参考链接:https://cloud.tencent.com/developer/article/1532994

              springboot监听器的使用(ApplicationListener、SmartApplicationListener、@EventListener),第5张

              先来看一下 spring的内置事件

              内置事件: 参考链接: https://blog.csdn.net/liyantianmin/article/details/81017960

              事件说明
              ContextRefreshedEventApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext接口中使用 refresh() 方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用。
              ContextStartedEvent当使用 ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
              ContextStoppedEvent当使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作。
              ContextClosedEvent当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。
              RequestHandledEvent这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件。

              自定义监听事件:

              • extends ApplicationEvent 自定义事件

                public class MyEvent extends ApplicationEvent {
                    private String time = new SimpleDateFormat("hh:mm:ss").format(new Date());
                    private String msg;
                    public MyEvent(Object source, String msg) {
                        super(source);
                        this.msg = msg;
                    }
                    public MyEvent(Object source) {
                        super(source);
                    }
                    public String getTime() {
                        return time;
                    }
                    public void setTime(String time) {
                        this.time = time;
                    }
                    public String getMsg() {
                        return msg;
                    }
                    public void setMsg(String msg) {
                        this.msg = msg;
                    }
                }
                
              • 监听器

                @Slf4j
                @Component
                public class MyTask implements ApplicationListener {
                    private static boolean aFlag = false;
                    @Override
                    public void onApplicationEvent(ApplicationEvent event) {
                        if (event instanceof ContextRefreshedEvent) {
                            log.info("监听到 ContextRefreshedEvent...");
                        }
                        if (event instanceof MyEvent) {
                            log.info("监听到 MyEvent...");
                            MyEvent myEvent = (MyEvent) event;
                            System.out.println("时间:" + myEvent.getTime() + " 信息:" + myEvent.getMsg());
                        }
                    }
                }
                
              • 触发事件

                自定义监听事件需要主动触发

                @SpringBootApplication
                public class TaskApplication {
                    public static void main(String[] args) {
                        ConfigurableApplicationContext run = SpringApplication.run(TaskApplication.class, args);
                        MyEvent event = new MyEvent("event", "忙中岁月忙中遣,我本愚来性不移");
                        // 发布事件
                        run.publishEvent(event);
                    }
                }
                

                也可以这样触发,美观一点

                @SpringBootApplication
                public class TaskApplication implements CommandLineRunner {
                    public static void main(String[] args) {
                        SpringApplication.run(TaskApplication.class, args);
                    }
                    @Resource
                    private ApplicationContext applicationContext;
                    @Override
                    public void run(String... args) throws Exception {
                        MyEvent event = new MyEvent("event", "忙中岁月忙中遣,我本愚来性不移");
                        // 发布事件
                        applicationContext.publishEvent(event);
                    }
                }
                
              • 控制台输出

                springboot监听器的使用(ApplicationListener、SmartApplicationListener、@EventListener),第6张

                2. SmartApplicationListener

                1. 简单使用

                @Slf4j
                @Component
                public class MyTask implements SmartApplicationListener {
                    @Override
                    public boolean supportsEventType(Class eventType) {
                        return eventType == MyEvent.class || eventType == ContextRefreshedEvent.class;
                    }
                    @Override
                    public int getOrder() {
                        return 0;
                    }
                    @Override
                    public void onApplicationEvent(ApplicationEvent event) {
                        if (event instanceof ContextRefreshedEvent) {
                            log.info("监听到 ContextRefreshedEvent...");
                        }
                        if (event instanceof MyEvent) {
                            log.info("监听到 MyEvent...");
                            MyEvent myEvent = (MyEvent) event;
                            System.out.println("时间:" + myEvent.getTime() + " 信息:" + myEvent.getMsg());
                        }
                    }
                }
                
                springboot监听器的使用(ApplicationListener、SmartApplicationListener、@EventListener),第7张

                2. 方法介绍

                public interface SmartApplicationListener extends ApplicationListener, Ordered {
                	boolean supportsEventType(Class eventType);
                	default boolean supportsSourceType(@Nullable Class sourceType) {
                		return true;
                	}
                	@Override
                	default int getOrder() {
                		return LOWEST_PRECEDENCE;
                	}
                	default String getListenerId() {
                		return "";
                	}
                }
                
                方法说明
                supportsEventType确认当前监听器是否支持当前事件类型。
                supportsSourceType确定此监听器是否实际支持给定的源类型。
                getOrder确定此侦听器在同一事件的一组侦听器中的顺序。数值越小,优先级越高。
                getListenerId返回侦听器的可选标识符。

                3. @EventListener

                使用:

                @Slf4j
                @Component
                public class MyTask {
                    @EventListener
                    public void onApplicationEvent(ApplicationEvent event) {
                        if (event instanceof ContextRefreshedEvent) {
                            log.info("监听到 ContextRefreshedEvent...");
                        }
                        if (event instanceof MyEvent) {
                            log.info("监听到 MyEvent...");
                            MyEvent myEvent = (MyEvent) event;
                            System.out.println("时间:" + myEvent.getTime() + " 信息:" + myEvent.getMsg());
                        }
                    }
                }
                
                @Slf4j
                @Component
                public class MyTask {
                    @EventListener
                    public void MyEventListener(MyEvent event) {
                        log.info("监听到 MyEvent...");
                        MyEvent myEvent = (MyEvent) event;
                        System.out.println("时间:" + myEvent.getTime() + " 信息:" + myEvent.getMsg());
                    }
                    @EventListener
                    public void ContextRefreshedEventListener(MyEvent event) {
                        log.info("监听到 ContextRefreshedEvent...");
                    }
                }
                

                指定监听事件的类型:

                @EventListener(MyEvent.class)
                @EventListener({MyEvent.class, ContextRefreshedEvent.class})