<导航

SpringBoot之CommandLineRunner接口和ApplicationRunner接口

  我们在开发中可能会有这样的情景。需要在容器启动的时候执行一些内容。比如读取配置文件,数据库连接之类的。SpringBoot给我们提供了两个接口来帮助我们实现这种需求。这两个接口分别为CommandLineRunner和ApplicationRunner。

他们的执行时机在官网上有这么一段话描述的:

  If you need to run some specific code once the SpringApplication has started, you can implement the ApplicationRunner or CommandLineRunner interfaces. Both interfaces work in the same way and offer a single run method, which is called just before SpringApplication.run(…​) completes. 

我的理解是spring加载完所有需要预加载的东西(环境/配置)之后再来执行这个,因为这两个方法完全可以有对环境的依赖及其他模块的调用,如果没有其它需要执行的东西,这里执行完就会输出启动完成。

也就是说:该方法仅在SpringApplication.run(…)完成之前调用,是在容器启动成功时的最后一步回调

这两个接口中有一个run方法,我们只需要实现这个方法即可。这两个接口的不同之处在于:

ApplicationRunner中run方法的参数为ApplicationArguments,而CommandLineRunner接口中run方法的参数为String数组。下面我写两个简单的例子,来看一下这两个接口的实现。

CommandLineRunner

package com.zkn.learnspringboot.runner;
 
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
 
/**
 * Created by zkn on 2016/8/12.
 */
@Component
public class TestImplCommandLineRunner implements CommandLineRunner {
 
 
    @Override
    public void run(String... args) throws Exception {
 
        System.out.println("<<<<<<<<<<<<这个是测试CommandLineRunn接口>>>>>>>>>>>>>>");
    }
}

ApplicationRunner接口

package com.zkn.learnspringboot.runner;
 
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
 
/**
 * Created by zkn on 2016/8/12.
 * 注意:一定要有@Component这个注解。要不然SpringBoot扫描不到这个类,是不会执行。
 */
@Component
public class TestImplApplicationRunner implements ApplicationRunner {
 
 
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(args);
        System.out.println("这个是测试ApplicationRunner接口");
    }
}

@Order注解

  如果有多个实现类,而你需要他们按一定顺序执行的话,可以在实现类上加上@Order注解。@Order(value=整数值)。SpringBoot会按照@Order中的value值从小到大依次执行。Tips如果你发现你的实现类没有按照你的需求执行,请看一下实现类上是否添加了Spring管理的注解(@Component)。

如果你发现你的实现类没有按照你的需求执行,请看一下实现类上是否添加了Spring管理的注解(@Component)。

注意项

测试代码

@Component
public class RunService  implements CommandLineRunner {
 
    public void run(String... strings){
        int i =0;
        while(true){
            i++;
                try {
                    Thread.sleep(10000);
                    System.out.println("过去了10秒钟……,i的值为:"+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i==4){ //第40秒时抛出一个异常
                    throw new RuntimeException();
                }
                continue;
        }
    }
}

 

再次启动 spring boot 项目,看看日志,直接报错,启动异常了。项目进程直接shutdown关闭了。

2018-07-16 01:56:43.703  INFO 7424 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 2147483647
2018-07-16 01:56:43.703  INFO 7424 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2018-07-16 01:56:43.722  INFO 7424 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2018-07-16 01:56:43.750  INFO 7424 --- [           main] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references
2018-07-16 01:56:43.885  INFO 7424 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8088 (http)
过去了10秒钟……,i的值为:1
过去了10秒钟……,i的值为:2
过去了10秒钟……,i的值为:3
过去了10秒钟……,i的值为:4
2018-07-16 01:57:23.939  INFO 7424 --- [           main] utoConfigurationReportLoggingInitializer : 
 
Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2018-07-16 01:57:23.973 ERROR 7424 --- [           main] o.s.boot.SpringApplication               : Application startup failed
 
java.lang.IllegalStateException: Failed to execute CommandLineRunner
 at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:735) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
 at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:716) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
 at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:703) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:304) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
 at com.hello.word.WordParseApplication.main(WordParseApplication.java:15) [classes/:na]
Caused by: java.lang.RuntimeException: null
 at com.zhangwq.service.RunService.run(RunService.java:24) ~[classes/:na]
 at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:732) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
 ... 6 common frames omitted
 
2018-07-16 01:57:23.975  INFO 7424 --- [           main] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@14a4e18: startup date [Mon Jul 16 01:56:39 CST 2018]; root of context hierarchy
2018-07-16 01:57:23.975  INFO 7424 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 2147483647
2018-07-16 01:57:23.975  INFO 7424 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
 
Process finished with exit code 1

 

 说明启动 CommandLineRunner 的执行其实是整个应用启动的一部分,没有打印最后的启动时间,说明项目是在 CommandLineRunner 执行完成之后才启动完成的。此时 CommandLineRunner 的 run 方法执行的是一个循环,循环到第四次的时候,抛出异常,直接影响主程序的启动。

解决方案

这样的问题该如何解决呢?这个操作影响了主线程,那么我们是否可以重新开启一个线程,让他单独去做我们想要做的操作呢。

@Component
public class RunService  implements CommandLineRunner {
 
    public void run(String... strings){
        new Thread(){
            public void run() {
                int i = 0;
                while (true) {
                    i++;
                    try {
                        Thread.sleep(10000);
                        System.out.println("过去了10秒钟……,i的值为:" + i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (i == 4) { //第40秒时抛出一个异常
                        throw new RuntimeException();
                    }
                    continue;
                }
            }
        }.start();
    }
}

我们再看看这次的日志是什么样的

2018-07-16 02:05:52.680  INFO 7148 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 2147483647
2018-07-16 02:05:52.680  INFO 7148 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Context refreshed
2018-07-16 02:05:52.695  INFO 7148 --- [           main] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
2018-07-16 02:05:52.717  INFO 7148 --- [           main] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references
2018-07-16 02:05:52.815  INFO 7148 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8088 (http)
2018-07-16 02:05:52.819  INFO 7148 --- [           main] com.hello.word.WordParseApplication      : Started WordParseApplication in 3.406 seconds (JVM running for 4.063)
过去了10秒钟……,i的值为:1
过去了10秒钟……,i的值为:2
过去了10秒钟……,i的值为:3
过去了10秒钟……,i的值为:4
Exception in thread "Thread-10" java.lang.RuntimeException
 at com.zhangwq.service.RunService$1.run(RunService.java:26)

此时 CommandLineRunner 执行的操作和主线程是相互独立的,抛出异常并不会影响到主线程。程序打印了启动时间,并且 CommandLineRunner 中 run 方法报错后,应用程序并没有因为异常而终止。成功解决。

另外,将报异常的代码try-catch手动处理后,也是一种解决方式。

注意:前面的代码如果没有对应的任何处理方案,run方法中不报错的话,程序是能够正常运行的,调方法查库等都是正常执行的。只是当且仅当run方法报错时若将异常抛到外面,将影响到主线程而导致程序关闭。

 

posted @ 2019-04-13 17:50  字节悦动  阅读(525)  评论(0编辑  收藏  举报