写点什么

如何让 SpringBoot 项目启动时执行特定代码

  • 2023-03-08
    湖南
  • 本文字数:3415 字

    阅读完需:约 11 分钟

(其实直接在 main 方法里写也不是执行不了) 如果只是简单的一些语句,写在 main 中可能会方便一些

但如果需要调用 spring 容器中的对象可能会要吃瘪,因为 main 方法是 static 的,而获取 ioc 对象不能使用 static 直接获取(会报错) 当调用 @AutoWired 获得 ioc 容器中的对象时

@Autowiredprivate static TestService testService;
复制代码

Exception in thread "main" java.lang.NullPointerException

当调用 @Resource 获得 ioc 容器中的对象时

@Resourceprivate static TestService testService;
复制代码

Caused by: java.lang.IllegalStateException: @Resource annotation is not supported on static fields

两个函数接口

有两个函数接口类都可以实现

1.ApplicationRunner 接口

源码

package org.springframework.boot;
@FunctionalInterfacepublic interface ApplicationRunner { void run(ApplicationArguments args) throws Exception;}
复制代码

使用方法


使用 @SpringBootApplication 注释的 ApplicationMain 启动类实现 ApplicationRunner 接口,并实现 run 方法即可 在 main 方法将 spring 启动完成后会执行 run 方法中的程序


@SpringBootApplicationpublic class BootTestApplication implements ApplicationRunner{
public static void main(String[] args) { SpringApplication.run(BootTestApplication.class, args); }
@Override public void run(ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner.run"); }}
复制代码

2.CommandLineRunner 接口

源码

package org.springframework.boot;
@FunctionalInterfacepublic interface CommandLineRunner { void run(String... args) throws Exception;}
复制代码

使用方法


其实 CommandLineRunner 和 ApplicationRunner 使用方法差不多


使用 @SpringBootApplication 注释的 ApplicationMain 启动类实现接口,并实现 run 方法即可 在 main 方法将 spring 启动完成后会执行 run 方法中的程序


@SpringBootApplicationpublic class BootTestApplication implements CommandLineRunner {
public static void main(String[] args) { SpringApplication.run(BootTestApplication.class, args); }
@Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner.run"); }}
复制代码

为什么会被执行?两个接口谁先执行?为什么?

举个栗子

我们来看个例子 现在我们有一个 service 类

@Servicepublic class TestService {    public void test(String name){        System.out.println(name);    }}
复制代码

然后 main 启动类中同时实现 ApplicationRunner 和 CommandLineRunner 接口,并调用这个 service 的方法 看看是谁先被输出


@SpringBootApplicationpublic class BootTestApplication implements ApplicationRunner , CommandLineRunner {
@Autowired private TestService service;
public static void main(String[] args) { SpringApplication.run(BootTestApplication.class, args); }
@Override public void run(ApplicationArguments args) throws Exception { service.test("ApplicationRunner.run"); }
@Override public void run(String... args) throws Exception { service.test("CommandLineRunner.run"); }}
复制代码

结果

可以看到,无论运行几次,结果都是 ApplicationRunner 先执行,CommandLineRunner 后执行

ApplicationRunner.run CommandLineRunner.run

如图

简单源码分析

我们查看 SpringApplication.run(BootTestApplication.class, args) 一层一层被调用到

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); } }
复制代码

其中有一条 this.callRunners(context, applicationArguments);就是回调方法了,我们点开可以看到

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); } }}
复制代码

如果启动类是 ApplicationRunner 的实现类,那么会调用 runner.run(args); 也就是启动类中实现了 ApplicationRunner 的 run 方法

if(runner instanceof ApplicationRunner) {    this.callRunner((ApplicationRunner)runner, args);}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) { try { runner.run(args); } catch (Exception var4) { throw new IllegalStateException("Failed to execute ApplicationRunner", var4); }}
复制代码

如果启动类是 CommandLineRunner 的实现类,那么会调用 runner.run(args.getSourceArgs()); 也就是启动类中实现了 CommandLineRunner 的 run 方法

if (runner instanceof CommandLineRunner) {    this.callRunner((CommandLineRunner)runner, args);}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) { try { runner.run(args.getSourceArgs()); } catch (Exception var4) { throw new IllegalStateException("Failed to execute CommandLineRunner", var4); }}
复制代码

作者:ifyyf

链接:https://juejin.cn/post/7050361931210686472

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
如何让SpringBoot项目启动时执行特定代码_Java_做梦都在改BUG_InfoQ写作社区