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() 方法进行操作,如果还有别的方法欢迎讨论。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栗然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值