This article briefly talks about the hook interfaces CommandLineRunner and ApplicationRunner, which are sometimes referred to as Runner in the following.

Runner callback timing

Refer to the source code of the org.springframework.boot.SpringApplication#run() method. You can know the timing of the callbacks for CommandLineRunner and ApplicationRunner.

SpringApplication

Before all CommandLineRunner and ApplicationRunner callbacks, the following steps have been ensured to be executed.

  1. Environment built-in variables are created and properties are populated.
  2. Banner has been printed.
  3. ApplicationContext and BeanFactory are created and the context is refreshed (refreshContext), meaning that all singleton Beans have been initialized and their properties assembled.
  4. The Servlet container is started successfully, such as the built-in Tomcat and Jetty containers have been started normally and can receive and process requests normally.
  5. The startup information is finished printing, you will generally see log output like Started OrderExportApplication in XXX seconds (JVM running for YYY).

That is, the CommandLineRunner or ApplicationRunner callback can use all the property values that already exist in the single instance Bean and Environment built-in variables in the context, so many times the demo project will operate in the CommandLineRunner or ApplicationRunner.

Simple use of ## Runner

The only difference is that CommandLineRunner#run() receives arguments from the main method, which is an array of strings (an array of indefinite strings), while ApplicationRunner#run() receives arguments of type ApplicationArguments, and the corresponding implementation class is DefaultApplicationArguments.

The annotation @Component can be applied directly to the implementation class of CommandLineRunner or ApplicationRunner, as opposed to adding the corresponding implementation singleton to the Spring context.

Example.

1
2
3
4
5
6
7
8
9
@Slf4j
@Component
public class CustomCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        log.info("CustomCommandLineRunner runs...");
    }
}

It is also possible to act directly on the methods corresponding to the anonymous classes of CommandLineRunner through the @Bean annotation, e.g.

1
2
3
4
5
6
7
8
9
@Slf4j
@Configuration
public class CommandLineRunners {
    
    @Bean
    public CommandLineRunner commandLineRunner(){
        return args -> log.info("CommandLineRunners commandLineRunner");
    }
}

Or implement the CommandLineRunner interface directly in the startup class (“This approach is not recommended “).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Slf4j
@SpringBootApplication
public class Ch5Application implements CommandLineRunner {

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

    @Override
    public void run(String... args) throws Exception {
        log.info("Ch5Application CommandLineRunner runs...");
    }
}

Orderedinterface or the@Orderannotation to define the order ofRunner` callbacks, the smaller the specified order number, the higher the priority.

Runner usage scenarios

This subsection is a suggestion based on personal programming habits. When the Runner hook interface calls back “If an exception is thrown, it will directly cause the application process to exit “, so if the Runner callback method must pay attention to the exception catching and handling. Based on this feature, combined with the previous analysis of the callback timing of the Runner interface, the main scenarios it applies to are

  • Printing a log to mark the successful start of a service or to mark the successful loading of certain properties.
  • Setting property values or starting components, e.g., switching on certain components, loading some application-level cache, starting timed tasks, etc.
  • Preload data (more commonly used in some testing scenarios and can be used in conjunction with the @Profile annotation to specify a specific profile before it takes effect).
  • Need to use the main method’s inputs.

The following uses CommandLineRunner to start all Jobs in Quartz (remember to import the dependencies spring-boot-starter-quartz and quartz first), and for simplicity the scheduler uses the in-memory state.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Slf4j
@DisallowConcurrentExecution
public class SimpleJob extends QuartzJobBean {

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        log.info("SimpleJob run...");
    }
}

@Component
public class QuartzCommandLineRunner implements CommandLineRunner {

    @Autowired
    private Scheduler scheduler;

    @Override
    public void run(String... args) throws Exception {
        JobDetail job = JobBuilder.newJob(SimpleJob.class).storeDurably().withIdentity(JobKey.jobKey("SimpleJob")).build();
        // 30秒执行一次
        Trigger trigger = TriggerBuilder.newTrigger()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(30))
                .forJob(job).build();
        scheduler.scheduleJob(job, trigger);
    }
}

After starting the application, the logs are as follows.

CommandLineRunner

Reference https://juejin.cn/post/6850418113890074637