在Spring Boot命令行应用程序中加载应用程序上下文后执行代码

时间:2016-08-03 14:03:48

标签: java spring command-line spring-boot

我有一个Spring Boot命令行应用程序(即没有本地Web服务器):

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(LocatesRecApplication.class, args);
    }

}

单独的组件实现CommandLineRunner,解析程序参数并执行服务:

@Component
public class ApplicationRunner implements CommandLineRunner {

  @Autowired
  private MyService someService;

  @Override
  public void run(String... strings) throws Exception {         
     someService.run();
  } 

}

问题1

从日志记录的角度来看,一旦上面的应用程序完成执行应用程序逻辑(包含在CommandLineRunner实现组件的注入组件中),最后两条消息就会显示应用程序上下文已经启动,然后立即执行关闭。虽然应用程序按预期执行,但上述行并不直观:

2016-08-03 14:24:41.254 INFO 9176 --- [main] com.xx.yyy.MyApplication: Started MyApplication in 25.885 seconds (JVM running for 26.962)
2016-08-03 14:24:41.254 INFO 9176 --- [main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7193666c: startup date [Wed Aug 03 14:24:16 BST 2016]; root of context hierarchy

同样,如果在任何组件执行期间存在未被捕获的运行时异常,则堆栈跟踪将从该行开始:

java.lang.IllegalStateException: Failed to load ApplicationContext

再一次,这非常令人困惑,因为人们会直观地期望在执行应用程序逻辑之前加载应用程序上下文。

问题2

上面使用的代码模式会在使用新@Jsontest注释的单元测试中造成严重破坏。由于执行此类测试无法启用组件扫描,因此无法加载应用程序上下文,因为无法加载自动装配的依赖项(上面为SomeService):

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.xx.yyy.MyApplication$ApplicationRunner': Unsatisfied dependency expressed through constructor parameter 1: No qualifying bean of type [com.xx.yyy.service.MyService] found for dependency [com.xx.yyy.service.MyService]: expected at least 1 bean which qualifies as autowire candidate for this dependency.

目前的解决方法是将CommandLineRunner实现保留在与@SpringBootApplication类不同的类中,因此后者仅执行上述代码段。

问题

我想知道上述问题是否可以解决。也许通过一种不同的模式,使应用程序能够在应用程序上下文加载后运行?

2 个答案:

答案 0 :(得分:1)

CommandLineRunner界面似乎更能处理"初始化"类型的东西。正如您所注意到的,Spring Boot框架将其作为其启动的一部分运行,并且在完成之前不会考虑启动完成。

如果只有一个"主要"您的命令行应用程序要执行的任务,我建议将其作为应用程序的主要方法的一部分运行:

    try (final ConfigurableApplicationContext applicationContext = SpringApplication.run(MyApplication.class, args)) {
        applicationContext.getBean(MyClassThatDoesStuff.class).run();
    }

如果您的应用程序使用命令行参数,您还可以使用CommandLineRunner来处理设置适当的bean属性等。

此方法已完全启动Spring上下文,然后您选择要对上下文执行的操作,然后关闭并退出它。

答案 1 :(得分:0)

其中一件事显然正在发生:SomeService未在ApplicationRunner的上下文中初始化,或ApplicationRunner正在SomeService之前初始化。首先,我会确保两者是相同上下文配置的一部分。如果这不是问题,您可以将@Order注释添加到服务bean声明中,以确保它获得比CommandLineRunner更高的加载优先级:

@Order(1)
@Service
public class MyService { ... }

@Component
public class ApplicationRunner implements CommandLineRunner { ... }