【问题标题】:Order of execution between CommandLineRunner run() method and RabbitMQ listener() methodCommandLineRunner run() 方法和 RabbitMQ listener() 方法之间的执行顺序
【发布时间】:2021-07-01 03:01:05
【问题描述】:

我的 Spring Boot 应用程序正在通过 RabbitMQ 订阅一个事件。 另一个 Web 应用程序负责将事件发布到我的应用程序正在侦听的队列中。 该事件主要包含机构信息。 主应用程序类实现 CommandLineRunner 并覆盖 run() 方法。 这个 run() 方法调用一个方法来创建管理员用户。

当我的应用程序启动并且队列中已经存在一个事件时,我的应用程序中的侦听器应该更新管理员用户的机构 ID。 但是,看起来 createAdmin() 和 listener() 正在并行执行,并且机构 ID 永远不会更新。帮助我理解控制流。

参见下面的代码 sn-p 和打印语句的顺序。

@SpringBootApplication
public class UserManagementApplication implements CommandLineRunner{

    public static void main(String[] args)  {
        SpringApplication.run(UserManagementApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        
        createAdmin();
    }
    
    private void createAdmin() {
        System.out.println("************** createAdmin invoked *********************");
        Optional<AppUserEntity> user = appUserService.getUserByUserName("superuser");
        
        if(!user.isPresent()) {
            AppUserEntity superuser = new AppUserEntity();
            superuser.setUsername("superuser");
            superuser.setAppUserRole(AppUserRole.SUPERADMIN);
            
            superuser.setInstId(null); // will be set when Queue receives Institute information 
            
            appUserService.saveUser(superuser);
            System.out.println("************** superuser creation SUCCESSFUL *********************");
        }
    }
}

@Component
public class InstituteQueueListener {

    @RabbitListener(queues = "institute-queue")
    public void updateSuperAdminInstituteId(InstituteEntity institute) {
        
        System.out.println("************** RabbitListener invoked *********************");
        
        Long headInstituteId = institute.getInstId();
        
        Optional<AppUserEntity> user = appUserService.getUserByUserName("superuser");
        if(user.isPresent()) {
            System.out.println("************* superuser is present *****************");
            AppUserEntity superuser = user.get();
            superuser.setInstId(headInstituteId);
            System.out.println("************* Going to save inst Id = "+headInstituteId);
            appUserService.saveUser(superuser);
        }
        
        System.out.println("************** superuser is NOT present (inside Q listener)*********************");
    }

}

Order of print statements ....
(the queue already has event before running my application)
System.out.println("************** createAdmin invoked *********************");
System.out.println("************** RabbitListener invoked *********************");
System.out.println("************** superuser is NOT present (inside Q listener) *********************");
System.out.println("************** superuser creation SUCCESSFUL *********************");

【问题讨论】:

    标签: spring spring-boot rabbitmq


    【解决方案1】:

    当您启动应用程序时,任何CommandLineRunners 都会在主线程(您调用SpringApplication.run 的线程)上调用。一旦刷新了应用程序上下文并且其所有 bean 都已初始化,就会发生这种情况。

    @RabbitListener-annotated 方法由消息侦听器容器在容器启动并且消息可用时调用。容器作为正在刷新的应用程序上下文的一部分启动,因此在调用命令行运行程序之前启动。容器使用单独的线程池来调用其侦听器。

    这意味着您的侦听器方法可能会在您的命令行运行器之前、同时或之后调用,具体取决于队列中是否有消息(事件)。

    【讨论】:

    • 感谢您的解释。是否有任何解决方案可以确保在执行 listener() 方法之前完成 createAdmin()。我希望我的应用程序负责在应用程序启动时创建管理员用户。
    • 您可以将spring.rabbitmq.listener.direct.auto-startupspring.rabbitmq.listener.simple.auto-startup(取决于您使用的)设置为false,然后在命令行运行程序完成后手动启动容器。或者,除了实现CommandLineRunner,您还可以使用早于消息侦听器的阶段来实现SmartLifecycle