user839040
user839040

Reputation:

Execute code after loading application context in a Spring Boot command Line application

I have a Spring Boot command line application (i.e. no local web server):

@SpringBootApplication
public class MyApplication {

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

}

A separate component implements a CommandLineRunner, parses program arguments, and executes services:

@Component
public class ApplicationRunner implements CommandLineRunner {

  @Autowired
  private MyService someService;

  @Override
  public void run(String... strings) throws Exception {         
     someService.run();
  } 

}

Problem 1

From a logging perspective, once the above application has finished executing the application logic, as contained in the injected components of the CommandLineRunner implementation component, the last two messages reveal that the application context has started, before being immediately closed. Whilst the application executes as expected, the above lines are not intuitive:

2016-08-03 14:24:41.254 INFO 9176 --- [main] com.xx.yyy.MyApplication: Started MyApplication in 25.885 seconds (JVM running for 26.962)
2016-08-03 14:24:41.254 INFO 9176 --- [main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7193666c: startup date [Wed Aug 03 14:24:16 BST 2016]; root of context hierarchy

Similarly, if there is an uncaught run-time exception during any of the components' execution, the stack trace will start with the line:

java.lang.IllegalStateException: Failed to load ApplicationContext

Once again, this is very confusing, as one would intuitively expect the application context to load before executing application logic.

Problem 2

The code pattern used above causes havoc in unit tests that use the new @Jsontest annotation. As the execution of such tests does not enable component scanning, the application context fails to load because the autowired dependencies (SomeService, above) fail to load:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.xx.yyy.MyApplication$ApplicationRunner': Unsatisfied dependency expressed through constructor parameter 1: No qualifying bean of type [com.xx.yyy.service.MyService] found for dependency [com.xx.yyy.service.MyService]: expected at least 1 bean which qualifies as autowire candidate for this dependency.

Current workaround for this is to keep my CommandLineRunner implementation in a separate class from the @SpringBootApplication class, so the latter does nothing more than the above snippet.

Question

I wonder if the above problems can be resolved. Perhaps through a different pattern that would enable the application to run after the application context has loaded?

Upvotes: 3

Views: 4675

Answers (2)

user65839
user65839

Reputation:

The CommandLineRunner interface appears to be more to handle "initialization"-type stuff. As you've noticed, the Spring Boot framework runs them as part of its startup, and doesn't consider startup complete until they are completed.

If there's just one "main" task for your command line application to perform, I'd suggest running it as part of your application's main method:

    try (final ConfigurableApplicationContext applicationContext = SpringApplication.run(MyApplication.class, args)) {
        applicationContext.getBean(MyClassThatDoesStuff.class).run();
    }

If your application uses command-line arguments, you could also have a CommandLineRunner that handles setting appropriate bean properties or the like.

This approach has the Spring context completely started, and then you choose what you want to do with the context, and then you close and exit it.

Upvotes: 2

woemler
woemler

Reputation: 7169

One of two things is obviously happening: either SomeService is not being initialized in the context of your ApplicationRunner, or ApplicationRunner is being initialized before SomeService. I would make sure that the two are part of the same context configuration, first of all. If that is not the issue, you can add the @Order annotation to the service bean declaration to ensure that it gets a higher loading precedence than the CommandLineRunner:

@Order(1)
@Service
public class MyService { ... }

@Component
public class ApplicationRunner implements CommandLineRunner { ... }

Upvotes: 0

Related Questions