SpringBoot-CommandLineRunner、ApplicationRunner

背景摘要

我们如果想要在Spring容器初始化完成后执行一些操作,可以实现ApplicationListener<ContextRefreshedEvent>接口,但是这个时候,会存在一个问题,在web 项目中(spring mvc),系统会存在两个容器,一个是root application context ,另一个就是我们自己的 web-servlet context(作为root application context的子容器)。这种情况下,就会造成onApplicationEvent方法被执行两次。为了避免上面提到的问题,我们可以只在root application context初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理。

1
2
3
4
5
6
@Override  
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().getParent() == null) {
...
}
}

或者用@EventListener注解

1
2
3
4
5
6
7
8
@Component
public class MyListener {

@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
...
}
}

但是如果在Spring boot中我们怎么处理?当然可以用上面的方式,本文再介绍两种Spring Boot中特有的方式。

CommandLineRunnerApplicationRunner

源码分析

看一下SpringBoot的启动入口:SpringApplication.run(xx.class, args);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//run方法
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}

public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
//这里执行refreshContext之后,并传入参数
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
//调用回调
callRunners(context, args);
}

private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
//从容器中拿到两种Runner,分别调用其run方法
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<Object>(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);
}
}

从上面源码看到

  • CommandLineRunner和ApplicationRunner所接受的参数有些不同,ApplicationRunner是ApplicationArguments对象,而CommandLineRunner是ApplicationArguments对象中的sourceArgs属性,可见,ApplicationRunner能获取到更多的参数信息。
  • 两种Runner都是从容器中根据Bean类型取出,所以我们只要在容器中放入Runner的实例就可以。
  • @Bean或者@Compent都可以。
  • 也很容易看出,如果容器中存在两种Runner,ApplicationRunner先被执行,CommandLineRunner后被执行,当然如果存在多个相同类型的Runner实现,可以用@Order来指定执行顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@SpringBootApplication
public class Example {
public static void main(String[] args) {
SpringApplication.run( Example.class, args );
}

@Bean
public ApplicationRunner runnerOne(){
return new ApplicationRunner() {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("=======one========");
}
};
}
@Bean
public ApplicationRunner runnerOneOne(){
return new ApplicationRunner() {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("=======one one========");
}
};
}
@Bean
public CommandLineRunner runnerTwo(){
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
System.out.println("===========two===========");
}
};
}
}

结果

1
2
3
=======one========
=======one one ========
===========two===========

如果用@Bean实现,@Bean的写的顺序是跟执行顺序有关系的,加上@Order并不起作用,这与@Bean的加载机制有关系。

用@Compent实现,加上@Order是可以的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Component
@Order(2)
public class RunnerOne implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("=======one========");
}
}

@Component
@Order(1)
public class RunnerOneOne implements ApplicationRunner{
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("=======one one========");
}
}

@Component
public class RunnerTwo implements CommandLineRunner{
@Override
public void run(String... args) throws Exception {
System.out.println("===========two===========");
}
}

结果

1
2
3
=======one one ========
=======one========
===========two===========

参数类型

ApplicationRunner可以解析 --name=value 根据name获取value

但是CommandLineRunner只是获取参数值,比如”aaa”;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Return the collection of values associated with the arguments option having the
* given name.
* <ul>
* <li>if the option is present and has no argument (e.g.: "--foo"), return an empty
* collection ({@code []})</li>
* <li>if the option is present and has a single value (e.g. "--foo=bar"), return a
* collection having one element ({@code ["bar"]})</li>
* <li>if the option is present and has multiple values (e.g. "--foo=bar --foo=baz"),
* return a collection having elements for each value ({@code ["bar", "baz"]})</li>
* <li>if the option is not present, return {@code null}</li>
* </ul>
* @param name the name of the option
* @return a list of option values for the given name
*/
List<String> getOptionValues(String name);

参考

http://blog.csdn.net/gebitan505/article/details/55047819

http://www.jianshu.com/p/5d4ffe267596

坚持技术分享,您的支持将鼓励我继续创作!