spring boot 中监听器下 onApplicationEvent 方法被执行两次问题分析及解决

一、背景

  1. 项目中使用的技术栈是 spring cloud + spring boot 构建的分布式项目。

  2. 其中一个微服务下创建了一个监听者,用于项目启动时从 Apollo 中获取 kafka 的配置信息(地址、topic、等一些基本配置),然后利用加载的配置初始化 kafka。代码如下:
    代码

  3. 现在出现一个问题,项目启动的时候 onApplicationEvent() 方法每次都会执行两遍,kafka 也会初始化两遍,导致业务中发送 kafka 时会发送两遍同样的 kafka。
    启动时问题

二、分析

  1. 可以在 onApplicationEvent() 方法中添加图一中的各种参数,图二可以查看打印出来的内容。 注意:获取各种参数的语句可能会引起空指针异常比如 “event.getApplicationContext().getParent().getParent()”,可以根据自己的项目修改。
    图一:
    参数
    图二:
    打印参数
  2. 查看打印的两块内容中的“容器中参数 getSources:”内容,发现下面容器中的 “class com.example.demo.DemoApplication”是当前项目的启动类,也就是说它是通过启动类中 main() 方法传入的。而上面容器中的 “class org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration” 不是当前项目中自定义的类,那么应该是第三方框架中的类。
  3. 于是用快捷键 Ctrl+N 搜索该类,发现它是因为在 pom 文件引入了 该依赖< artifactId >spring-cloud-starter-bus-amqp</ artifactId> 项目启动时这个依赖包就会启动一个新的容器。如果在 pom 文件中把该依赖去掉,再启动项目会发现 onApplicationEvent() 方法只加载一次了,即上面的容器没有被加载。但显然这不是解决问题的方法,因为项目中要用到这个功能,如果项目中没有用到这个依赖可以考虑去掉,也就解决了问题。
    依赖包
  4. 再分析图二中的另一参数 “容器中参数 getApplicationName: ”。上面容器打印的是空字符串,下面容器打印的是“/demo”,其实下面容器中的“/demo”就是配置文件即 yml 中 server.context-path: 的值,如果 yml 中没有配置则会同上面容器一样显示空字符串。
    配置
  5. 分析图二中的参数 “容器中参数 getParent:” 和 “容器中参数 getParent.getParent:” 可以看出,其实是三个容器进行了嵌套。上面那个容器有两个父容器,下面那个容器有一个父容器。至于为什么出现这种情况可以自己看看源码、找找资料,或者有大佬评论区分享一下。

三、解决

根据上面的分析可以提供以下几种解决方案。

  1. 根据分析 3,可以看一下引起 onApplicationEvent() 方法被执行两次的依赖包项目中有没有用到,没有的话删除依赖就能解决问题。如果用到了再考虑下面的方法。
  2. 根据分析 4,可以通过下面方法解决。前提是配置文件中要有该配置。
@Component
public class DataInitializerListener implements ApplicationListener<ApplicationReadyEvent> {
	@Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
	    //思路:只有加载配置文件的容器启动时才执行该方法,通过下面的判断排除掉别的容器。
       if("/demo".equals(event.getApplicationContext().getApplicationName())){
           //功能代码
           System.out.println("kafka 配置");
       }
    }
 }
  1. 根据分析 5,可以通过下面方法解决,这也是网上最常见的方法。
@Component
public class DataInitializerListener implements ApplicationListener<ApplicationReadyEvent> {
	@Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
	    //思路:既然是多个容器嵌套,那么只让最外层的容器即父容器启动时执行该方法,其他的容器排除掉
       if(event.getApplicationContext().getParent().getParent() == null){
       //本人的项目中是三个容器嵌套,需要找两层父容器,如果是两个容器嵌套只需要找一层。判断条件需要改为:
       // if(event.getApplicationContext().getParent() == null){
           //功能代码
           System.out.println("kafka 配置");
       }
    }
 }
  1. 测试一下就会发现,类只加载了一遍,方法执行了两次,因此还有一种方法。
@Component
public class DataInitializerListener implements ApplicationListener<ApplicationReadyEvent> {
    private boolean flag = false;
	@Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
	    //思路:加一个标记,第一个容器启动时不执行此方法,第二个容器启动时才执行。
       if(flag){
           //功能代码
           System.out.println("kafka 配置");
       }else {
           flag = true;
       }
    }
 }

四、最后

  1. 解决完启动问题后还要测试一下,项目运行过程中触发监听者中的事件 onApplicationEvent() 内的功能代码还能否执行到。
  2. 这几个解决的方案都是对 onApplicationEvent() 方法进行操作,如果还有别的方法欢迎讨论。
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot监听器是用于监听应用程序特定事件的组件,当这些事件发生时,监听器执行相应的逻辑。 下面是一个简单的使用Spring Boot监听器的示例: 1. 创建一个自定义的监听器类,实现SpringApplicationListener接口。例如: ```java public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> { @Override public void onApplicationEvent(ApplicationReadyEvent event) { // 处理应用程序启动完成事件 System.out.println("应用程序已启动!"); } } ``` 2. 在Spring Boot应用程序的入口类,通过注解@EnableAutoConfiguration或@SpringBootApplication启用自动配置,并通过@ComponentScan扫描自定义监听器类。例如: ```java @EnableAutoConfiguration @ComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 3. 运行应用程序时,自定义监听器的onApplicationEvent方法将会在应用程序启动完成时被调用。例如,当应用程序启动完成时,控制台将会打印出"应用程序已启动!"。 此外,Spring Boot还提供了其他类型的监听器,用于监听不同类型的事件,如应用程序启动前事件、应用程序关闭事件等。你可以根据需要实现不同的监听器,并注册到Spring Boot应用程序。 总结:Spring Boot监听器用于监听特定事件,并在事件发生时执行相应的逻辑。通过自定义监听器类和在入口类注册监听器,我们可以方便地使用监听器来处理应用程序的各种事件。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值