A map to help you remember, Spring Boot application code execution in the start-up phase in several ways

Foreword

Sometimes we need to perform some code snippets when the application starts, these fragments may be simply to record log, it may be the time to start checking and installing the certificate , such as the above business requirements we may often encounter

Spring Boot provides at least 5 ways to execute code when the application starts. How should we choose? This article will explain step by step and analyze this in several different ways


CommandLineRunner

CommandLineRunnerIs an interface by implementing it, we can Spring 应用成功启动之后execute some code snippets

@Slf4j
@Component
@Order(2)
public class MyCommandLineRunner implements CommandLineRunner {
    
    @Override
    public void run(String... args) throws Exception {
        log.info("MyCommandLineRunner order is 2");
        if (args.length > 0){
            for (int i = 0; i < args.length; i++) {
                log.info("MyCommandLineRunner current parameter is: {}", args[i]);
            }
        }
    }
}

When Spring Boot found in the application context CommandLineRunnerbean, which will be called after the successful launch of the application run()method and pass command line arguments to start the application

Maven jar package generated by the command:

mvn clean package

Command to start the application by the terminal, and pass parameters:

java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar --name=rgyb

View the results:

Here we can see a few questions:

  1. Command line parameters passed has not been resolved, but only shows that we passed in the string contents --foo=bar, --name=rgybwe can ApplicationRunnerresolve, we see later
  2. In rewriting run()there are methodological throws Exceptionmark, Spring Boot will be CommandLineRunnerapplied as part of the launch, if you run run()throws Exception time method, the application will terminate start
  3. We added on the class @Order(2)notes, when there is more CommandLineRunnertime, will follow @Orderfrom small to large (digital of course, can also be used in the plural) annotation of digital

⚠️ Do not use @Ordertoo much

Order to see this "black technology" we will find it very easy to start logic executed in a specified order, but if you are so write, illustrate various snippets are interdependent relationship, in order to make our code more maintainable, we should reduce this reliance on the use

summary

If we just want to get a simple command-line parameters separated by spaces, it MyCommandLineRunneris sufficient to use the


ApplicationRunner

As mentioned above, the command line to start and pass parameters, MyCommandLineRunneryou can not resolve the argument, if you want to parse parameters, then we have to use the ApplicationRunnerargument of

@Component
@Slf4j
@Order(1)
public class MyApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("MyApplicationRunner order is 1");
        log.info("MyApplicationRunner Current parameter is {}:", args.getOptionValues("foo"));
    }
}

Re-jar packaging, run the following command:

java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar,rgyb

Results are as follows:

Here we can see:

  1. With MyCommandLineRunnersimilar, but ApplicationRunnercan be run by the method of ApplicationArgumentsparsing the command line parameter object, and each parameter which can have multiple values, since the getOptionValuesmethod returns List Array
  2. In rewriting run()there are methodological throws Exceptionmark, Spring Boot will be CommandLineRunnerapplied as part of the launch, if you run run()throws Exception time method, the application will terminate start
  3. ApplicationRunnerYou can also use @Orderannotations to sort, starting from the results, it CommandLineRunnerorder to share the order, later on we verify this conclusion by source

summary

If we want to get complicated command-line parameters, we can use ApplicationRunner


ApplicationListener

If we do not get command-line arguments, we can start to bind to the logic of Spring ApplicationReadyEventon

@Slf4j
@Component
@Order(0)
public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        log.info("MyApplicationListener is started up");
    }
}

Run the program to see the results:

With this we can see:

  1. ApplicationReadyEvent If and only if was only triggered after the application is ready, and even said that will only be triggered after the above article in the Listener to say that all solutions are implemented, the final conclusions please see later
  2. I use the code Order(0)marked clearly ApplicationListener also possible to sort by this comment, by numerical order size, should be executed first. However, this order only for the same type of ordering between the ApplicationListener, previously mentioned ApplicationRunnersand CommandLineRunnerssorted do not share

summary

If we do not get command-line arguments, we can ApplicationListener<ApplicationReadyEvent>, we can get through it to create some global startup logic configuration properties environment variable parameters Spring Boot Support


If you read my writing before the Spring Bean Life Cycle trilogy:

Then you will have the following two ways very familiar

@PostConstruct

Create a boot logical Another simple solution is to provide a method for initializing calls during the Spring created in the bean. We need to do is to @PostConstructadd annotations to methods:

@Component
@Slf4j
@DependsOn("myApplicationListener")
public class MyPostConstructBean {

    @PostConstruct
    public void testPostConstruct(){
        log.info("MyPostConstructBean");
    }
}

View the results:

As seen from the above results run:

  1. Spring bean has been created after ( before the start ), it will immediately call the @PostConstructmethod annotated markers, so we can not use @Orderannotations to sort them free, because it may depend on @Autowiredinsertion into other Spring bean our bean.
  2. Instead, it will be called after all it depends on the bean is initialized, if you want to add artificial dependency and thus create a sort, you can use the @DependsOnannotation (although you can sort, but does not recommend the use of the grounds and @Orderthe same)

summary

@PostConstruct 方法固有地绑定到现有的 Spring bean,因此应仅将其用于此单个 bean 的初始化逻辑;


InitializingBean

@PostConstruct 解决方案非常相似,我们可以实现 InitializingBean 接口,并让 Spring 调用某个初始化方法:

@Component
@Slf4j
public class MyInitializingBean implements InitializingBean {


    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("MyInitializingBean.afterPropertiesSet()");
    }
}

查看运行结果:

从上面的运行结果中,我们得到了和 @PostConstruct 一样的效果,但二者还是有差别的

⚠️ @PostConstructafterPropertiesSet 区别

  1. afterPropertiesSet,顾名思义「在属性设置之后」,调用该方法时,该 bean 的所有属性已经被 Spring 填充。如果我们在某些属性上使用 @Autowired(常规操作应该使用构造函数注入),那么 Spring 将在调用afterPropertiesSet 之前将 bean 注入这些属性。但 @PostConstruct 并没有这些属性填充限制
  2. 所以 InitializingBean.afterPropertiesSet 解决方案比使用 @PostConstruct 更安全,因为如果我们依赖尚未自动注入的 @Autowired 字段,则 @PostConstruct 方法可能会遇到 NullPointerExceptions

小结

如果我们使用构造函数注入,则这两种解决方案都是等效的


源码分析

请打开你的 IDE (重点代码已标记注释):

MyCommandLineRunnerApplicationRunner 是在何时被调用的呢?

打开 SpringApplication.java 类,里面有 callRunners 方法

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    //从上下文获取 ApplicationRunner 类型的 bean
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());

    //从上下文获取 CommandLineRunner 类型的 bean
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());

    //对二者进行排序,这也就是为什么二者的 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);
        }
    }
}

强烈建议完整看一下 SpringApplication.java 的全部代码,Spring Boot 启动过程及原理都可以从这个类中找到一些答案


总结

最后画一张图用来总结这几种方式(高清大图请查看原文:https://dayarch.top/p/spring-boot-execute-on-startup.html)

灵魂追问

  1. 上面程序运行结果, afterPropertiesSet 方法调用先于 @PostConstruct 方法,但这和我们在 Spring Bean 生命周期之缘起 中的调用顺序恰恰相反,你知道为什么吗?
  2. MyPostConstructBean 通过 @DependsOn("myApplicationListener") 依赖了 MyApplicationListener,为什么调用结果前者先与后者呢?
  3. 为什么不建议 @Autowired 形式依赖注入

在写 Spring Bean 生命周期时就有朋友问我与之相关的问题,显然他们在概念上有一些含混,所以,仔细理解上面的问题将会帮助你加深对 Spring Bean 生命周期的理解

欢迎持续关注公众号:「日拱一兵」

  • 前沿 Java 技术干货分享
  • 高效工具汇总 | 回复「工具」
  • 面试问题分析与解答
  • 技术资料领取 | 回复「资料」

以读侦探小说思维轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......


Guess you like

Origin www.cnblogs.com/FraserYu/p/12117888.html