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方法报错时若将异常抛到外面,将影响到主线程而导致程序关闭。