Execute when springboot starts: CommandLineRunner, ApplicationRunner, ApplicationListener @PostConstruct comparison
Usage scenario:
During the development process, we will have such a scenario: we need to perform some operations after the project is started, such as reading configuration file information, database connection, deleting temporary files, clearing cache information, factory class initialization, loading active data, or cache synchronization. We have many implementation methods, such as @ PostConstruct, CommandLineRunner, ApplicationRunner and ApplicationListener. We can implement our specific logic after springboot is started. Next, compare their differences
1 @PostConstruct
This annotation is used to modify a non static void method. The method modified by @ PostConstruct will run when the server loads the Servlet and will only be executed by the server once.
Trigger timing:
SpringBoot will automatically initialize the global single instance of the class or interface marked with Bean related annotations (such as @ Component, @ Service, @ Repository, etc.). If marked, the initialization order will be in the order marked by the user, otherwise it will be initialized in the default order. During initialization, after executing the construction method of a Bean, the @ PostConstruct method (if any) of the Bean will be executed, and then the next Bean will be initialized.
bean creation process in spring
Configure beans (@ component, @ Service, @ Controller and other annotation configurations) ---- > resolve to Bean metadata (BeanDefinition object in Bean container) ---- > generate beans according to Bean metadata (create beans)
Execution order when creating bean s
Constructor - > @ Autowired - > @ postconstruct (annotated method)
Example:
@PostConstruct public void dispatcher() throws Exception { // Logic code }
advantage:
- It's easy to use. Just add this annotation to the class managed by the spring container
Disadvantages:
- It is triggered when spring creates beans. At this time, the container has not been fully initialized. If the logic references beans that have not been initialized, an exception will be caused. Therefore, the loading order needs to be considered.
- If the logic processing time in the @ PostConstruct method is long, it will increase the time for the SpringBoot application to initialize the Bean, and then increase the time for the application to start. Because the SpringBoot application will open the port to provide services only after the Bean initialization is completed, the application cannot be accessed before that.
- Bottom line: it will affect the time when your program starts.
2 ,CommandLineRunner,ApplicationRunner
It is very simple to use. You only need to implement the CommandLineRunner or ApplicationRunner interface and rewrite the run method.
Trigger timing:
Start the source code through springboot:
After startup, the callRunners method will be executed;
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); //Set thread start timer stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //Configure system properties: default: the external display screen is missing and can be started configureHeadlessProperty(); //Get and start the event listener. If there are no other listeners in the project, there is only EventPublishingRunListener by default SpringApplicationRunListeners listeners = getRunListeners(args); //Broadcast events to listeners listeners.starting(); try { //For implementing the ApplicationRunner interface, the user sets the ApplicationArguments parameter for encapsulation ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //Configure the running environment: for example, activate the application * * * yml profile ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); //Load the configured banner(gif,txt...), Console drawing Banner printedBanner = printBanner(environment); //Create a context object and instantiate it context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //Configuring the SPring container prepareContext(context, environment, listeners, applicationArguments, printedBanner); //Refresh the Spring context and create the bean refreshContext(context); //Empty method, subclass implementation afterRefresh(context, applicationArguments); //Stop timer: calculates the thread start time stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } //Stop event listener listeners.started(context); //Start loading resources callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, listeners, exceptionReporters, ex); throw new IllegalStateException(ex); } listeners.running(context); return context; }
callRunners method:
private void callRunners(ApplicationContext context, ApplicationArguments args) { //Store the classes that implement the ApplicationRunner and CommandLineRunner interfaces into the collection List<Object> runners = new ArrayList<>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); //Sort by loading order AnnotationAwareOrderComparator.sort(runners); for (Object runner : new LinkedHashSet<>(runners)) { if (runner instanceof ApplicationRunner) { callRunner((ApplicationRunner) runner, args); } if (runner instanceof CommandLineRunner) { callRunner((CommandLineRunner) runner, args); } } }
As can be seen from the above source code, after the springboot is fully initialized, CommandLineRunner and ApplicationRunner will be executed. The only difference between the two is that the parameters are different, but they will not affect. The execution parameters can be obtained.
Example
/** * @author * @date 2021-08-23 16:19 */ @Component public class ServerDispatcher implements CommandLineRunner { @Override public void run(String... args){ // Logic code } }
/** * @author * @date 2021-08-23 16:19 */ @Component public class ServerDispatcher2 implements ApplicationRunner { @Override public void run(ApplicationArguments args){ // Logic code } }
3,ApplicationListener
Through event listening, we can also implement the springboot startup execution method. Implement ApplicationListener and override onApplicationEvent method to execute after all bean s are loaded.
Trigger timing:
During the startup process of the IOC container, when all bean s have been processed, the spring ioc container will have an action to publish the ContextRefreshedEvent event.
Example
/** * @author * @date 2021-08-23 16:19 */ @Component public class ServerDispatcher3 implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { // Logic code } }
be careful:
There will be two containers in the system, one is root application context, and the other is our own ProjectName servlet context (as a sub container of root application context)
- ApplicationContext
- context.pushevent()
- WebtapplicationContext
- context.pushevent()
In this case, the onApplicationEvent method will be executed twice. To avoid the problems mentioned above, we can call logic code only after initialization of root application context, and do not deal with other container initialization.
//root application context has no parent if (event.getApplicationContext().getParent() == null) { //Logic code }
summary
- Some independent and compact initialization logic will not affect the startup speed of springboot. Use @ PostConstruct annotation;
- If you want to listen through the ApplicationListener event, you need to handle the specified container.
- At the data initialization level, @ PostConstruct and ApplicationListener are not recommended because both will affect the startup of the program. If the execution of logic takes a long time, it will take a long time to start the service.
- I suggest using CommandLineRunner and ApplicationRunner, which will not affect the startup speed of the service and is relatively simple to handle.