Search code examples
javaspring-bootautowiredpostconstructspring-scheduled

Wait for dependencies to load before starting spring boot service


I have a spring boot application which has two beans - AppState and Users. AppState depends on the bean Users as it autowires it. The skeleton codes for the two beans are as follows.

@Component
@EnableScheduling
public class Users {
    @Getter // lombok
    private List<String> users;

    @PostConstruct
    public void init(){
        users = new ArrayList<>();
        load(); // I removed this later
    }

    @Scheduled(fixedRate = 3600000)
    public void load(){
        // load list of users from (say) a file and populate ArrayList 'users'
        // this method takes at least 3 mins to finish
    }
}

AppState is

@Component
public class AppState {
    @Atowired
    private Users users;

    public List<String> getUsers(){
        return users.getUsers();
    }
}

I noticed that the method load() in Users was getting triggered twice - probably once during init() and the other time while scheduling load() right after Users bean had been created. So I removed the call to load() in init(). This fixed the redundant call issue.

However, now I find that my service starts as soon as AppState and Users beans have been created, even though Users has not been populated with data yet. This is risky for me as the service, during this time, will return 0 users if queried.

I need help with ANY of the following.

  • Should I move load() back into init() to make sure that when the bean is done with PostConstruct, it does have all users info? If I go this route, how can I prevent redundant run of load()?

  • If load() should stay out of init(), how can I ensure that AppState is not ready unless Users has executed load()? I tried using the following code in AppState but it just hangs.

The code is as follows.

@PostConstruct
public void appStateInit(){
    while(users.getUsers().size()==0){
        try{
            Thread.sleep(10000); // sleep 10s
        }catch(whatever){
        }
    }
}

Solution

  • I would suggest having a flag in the class and setting it to true once init() has finished. You can skip the execution of load() if flag is not set yet, e.g.:

    private AtomicBoolean shouldExecute;
    
    @PostConstruct
        public void init(){
            users = new ArrayList<>();
            shouldExecute = true;
        }
    
    @Scheduled(fixedRate = 3600000)
    public void load(){
        if(shouldExecute){
           // load list of users from (say) a file and populate ArrayList 'users'
        // this method takes at least 3 mins to finish
        } 
    }
    

    Also, another solution will be to configure initialDelay in @Scheduled annotation (documentation here) which would delay the first execution by configured number of milliseconds, e.g:

        @Scheduled(fixedRate = 3600000, initialDelay=180000)
        public void load(){
            // load list of users from (say) a file and populate ArrayList 'users'
            // this method takes at least 3 mins to finish
        }