SpringBoot入坑指南之三:業務初始化

概述

在實際項目開發過程當中,有時候須要在服務啓動時進行一些業務初始化操做,這些操做只須要在服務啓動後執行一次,那麼經過Spring Boot如何實現該需求呢? Spring Boot提供了ApplicationRunner和CommandLineRunner兩種服務接口,這兩種服務接口均可以實現上面的業務需求,本文將對這兩種服務接口實現進行介紹。git

ApplicationRunner與CommandLineRunner

異同點

  • 相同點spring

    • 二者均在服務啓動完成後執行,而且只執行一次。
    • 二者都能獲取到應用的命令行參數。
    • 二者觸發執行的時間點是一致的。
  • 不一樣點app

    • 雖然二者都是獲取到應用的命令行參數,可是ApplicationRunner獲取到的是封裝後的ApplicationArguments對象,而CommandLine獲取到的是ApplicationArguments中的sourceArgs屬性(List<String>),即原始參數字符串列表.

執行順序

不少誤認爲CommandLineRunner會先於ApplicationRunner執行,可是實際上二者是一塊兒觸發執行的,能夠閱讀SpringApplication.class方法中的源碼less

  • 1.SpringApplication.class中的run方法,會在執行完一些列初始化工做以後,調用callRunners方法執行Runner中的相關初始化代碼。
public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
  • 2.在callRunners方法源碼中,能夠看出,ApplicationRunner和CommandLineRunner會被加載到同一個List中,以後排序並循環執行,因此並無CommandLineRunner先執行一說。實際上,若是沒有指定執行順序,默認是ApplicationRunner先執行的。
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);
        Iterator var4 = (new LinkedHashSet(runners)).iterator();

        while(var4.hasNext()) {
            Object runner = var4.next();
            if (runner instanceof ApplicationRunner) {
                this.callRunner((ApplicationRunner)runner, args);
            }

            if (runner instanceof CommandLineRunner) {
                this.callRunner((CommandLineRunner)runner, args);
            }
        }

    }

實現示例

1.實現Runner服務接口

  • 建立ApplicationRunnerFirst和ApplicationRunnerSecond兩個類,實現ApplicationRunner接口。
@Component
@Slf4j
public class ApplicationRunnerFirst implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("This is {} Application Runner", "first");
    }
}
@Component
@Slf4j
public class ApplicationRunnerSecond implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("This is {} Application Runner", "second");
    }
}
  • 建立兩個類,實現CommandLineRunner接口。
@Component
@Slf4j
public class CommandlineRunnerFirst implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("CommandLineRunner Args:{}",JSON.toJSONString(args));
        log.info("This is {} Command Line Runner", "first");
    }
}
@Component
@Slf4j
public class CommandlineRunnerSecond implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("This is {} Command Line Runner", "second");
    }
}

2.指定執行順序

可使用org.springframework.core.annotation.Order註解設置執行順序,其中數值越小越優先執行。例如:ide

@Component
@Slf4j
@Order(3)
public class ApplicationRunnerFirst implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("ApplicationRunner Args:{}",JSON.toJSONString(args));
        log.info("This is {} Application Runner", "first");
    }
}

分別將CommandlineRunnerSecond的Order設置爲1,ApplicationRunnerSecond設置爲2,ApplicationRunnerFirst設置爲3,CommandlineRunnerFirst不設置。spring-boot

3.運行結果

啓動服務,運行結果以下:this

啓動日誌

你會發現,未指定順序的CommandlineRunnerFirst在最後執行,那是由於若是沒有設置順序,運行時排序使用的數值是整型最大值2147483647(@Order註解的默認值也是整型最大值),詳細可閱讀OrderComparator.class源碼。 還有需注意的是,Runner的實現類必須註冊爲Spring Bean,不然不回被執行,閱讀SpringApplication.run方法的源碼就知道緣由了。命令行

示例源碼

碼雲:https://gitee.com/centy/spring-boot-examples/tree/master/spring-boot-examples-runner日誌

尾巴

不管ApplicationRunner仍是CommandLineRunner,都是在應用啓動完成後執行一次業務初始化代碼,達到的效果也比較相似,因爲ApplicationRunner的方法參數是ApplicationArguments對象,使用起來更加方便,因此更推薦使用。code

相關文章
相關標籤/搜索