SpringBoot启动时加载

一、CommandLineRunner和ApplicationRunner

1.1 应用及场景

SpringBoot中提供了两个接口可以在SpringBoot启动的过程中进行一些额外的操作,比如读取配置文件、数据库操作等自定义的内容。而这些功能的实现也非常简单,直接实现这两个接口并实现其run方法,然后将该类实例化即可。以下代码便实现了CommandLineRunner接口,并在run方法内打印了对应的日志,同时,通过@Component将其注册为Spring的一个bean

@Component
public class LearnCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) {
        System.out.println("LearnCommandLineRunner running");
    }
}

1.2 源代码

下面看一下CommandLineRunnerApplicationRunner的源代码:

public interface CommandLineRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming main method arguments
     * @throws Exception on error
     */
    void run(String... args) throws Exception;

}
public interface ApplicationRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming application arguments
     * @throws Exception on error
     */
    void run(ApplicationArguments args) throws Exception;

}

通过源代码的对比会发现,它们唯一不同便是run方法的参数。在实际应用中它们的区别也只有这些。

1.3 执行顺序

通过接口的官方文档,我们得知其实执行CommandLineRunnerApplicationRunner的实现类是有顺序的,只不过在示例中并没有展示。针对上面的示例,我们可以通过@Order或实现Ordered接口来对其指定执行顺序。

@Order(value = 1)
@Component
public class LearnCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) {
        System.out.println("LearnCommandLineRunner running");
    }
}

1.4 使用源码分析

说到执行顺序,那么再进一步了解一下这两个方法是在什么时候执行的。这两个接口的实现执行的时机在于SpringApplication初始化之后,调用的run方法中被调用的。

public ConfigurableApplicationContext run(String... args) {
    // 创建StopWatch对象,用于统计run方法启动时长
    StopWatch stopWatch = new StopWatch();
    // 启动统计
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 配置headless属性
    configureHeadlessProperty();
    // 获得SpringApplicationRunListener数组,
    // 该数组封装于SpringApplicationRunListeners对象的listeners中。
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 启动监听,遍历SpringApplicationRunListener数组每个元素,并执行。
    listeners.starting();
    try {
        // 创建ApplicationArguments对象
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 加载属性配置,包括所有的配置属性(如:application.properties中和外部的属性配置)
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 打印Banner
        Banner printedBanner = printBanner(environment);
        // 创建容器
        context = createApplicationContext();

        // 异常报告器
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // 准备容器,组件对象之间进行关联
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 初始化容器
        refreshContext(context);
        // 初始化操作之后执行,默认实现为空。
        afterRefresh(context, applicationArguments);
        // 停止时长统计
        stopWatch.stop();
        // 打印启动日志
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        // 通知监听器:容器启动完成。
        listeners.started(context);
        // 调用ApplicationRunner和CommandLineRunner的运行方法。
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        // 异常处理
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 通知监听器:容器正在运行。
        listeners.running(context);
    } catch (Throwable ex) {
        // 异常处理
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

我们可以看到,在try方法的最后,会执行一个callRunners的方法,在此方法中会对实现这两个接口的实现类进行调用。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    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);
        }
    }
}

private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
    try {
        (runner).run(args);
    } catch (Exception ex) {
        throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
    }
}

private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
    try {
        (runner).run(args.getSourceArgs());
    } catch (Exception ex) {
        throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
    }
}

通过以上代码,我们也就了解到这两个接口的实现类的执行时机了。

二、@PostConstruct

另一个需求是,在类加载的时候,为当前类初始化一些数据,那么可以使用@PostConstruct注解。

Servlet中增加了两个影响Servlet生命周期的注解,@PostConstruct@PreDestroy,这两个注解被用来修饰一个非静态的void()方法。

在一个类内,如果有构造器(Constructor),有@PostConstruct,还有@Autowired,他们的先后执行顺序为Constructor >> @Autowired >> @PostConstruct

因为一个有声明注解的类文件(必须有声明,这样在项目初始化时候才会注入),在项目启动后,会对对象进行依赖注入,而初始化的动作会依赖于对象,所以假象上看,也类似于项目启动就会执行的操作,因此,我们也可以通过这样的形式,对数据进行初始化。

说明一下@PostConstruct更针对性于当前类文件,而CommandLineRunner更服务于整个项目。所以在我们使用中,可根据自己的使用场景来进行选择用这两种方式来实现初始化。

import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;

@Component
public class Init {

    @PostConstruct
    private void init(){
        System.out.println("PostConstruct注解 初始化数据.");
    }
}

说明一下:执行结果可以看到,在项目还没有启动成功的时候,@PostConstruct已经执行完了,因为@PostConstruct是在Init类注入完成后立马执行的,它并不依赖于项目的启动。

posted @ 2022-04-25 16:48  夏尔_717  阅读(711)  评论(0编辑  收藏  举报