Order of execution between CommandLineRunner run() method and RabbitMQ listener() method

Viewed 282

My Spring Boot application is subscribing to an event via RabbitMQ. Another web application is responsible for publishing the event to the queue which my application is listening to. The event basically contains institute information. The main application class implements CommandLineRunner and overrides run() method. This run() method invokes a method to create admin user.

When my application is started, and when an event is already present in queue, the listener in my application is supposed to update the Admin user's institute id. However it looks like createAdmin() and the listener() are getting executed in parallel and institute id is never updated. Help me in understanding the control flow.

See below the code snippet and the order of print statements.

@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 *********************");
1 Answers

When you start your application, any CommandLineRunners are called on the main thread (the thread on which you called SpringApplication.run). This happens once the application context has been refreshed and all of its beans have been initialized.

@RabbitListener-annotated methods are called by a message listener container once the container has been started and as messages become available. The container is started as part of the application context being refreshed and, therefore, before your command line runner is called. The container uses a separate pool of threads to call its listeners.

This means that your listener method may be called before, at the same time, or after your command line runner, depending on whether there is a message (event) on the queue.

Related