结论
- 首先先将结论奉上
- 实现CommandLineRunner接口的类,需要实现run方法,项目启动时,会由主线程去执行实现了CommandLineRunner接口类的run方法。
- 如果优先执行的run中有阻塞方法,会导致线程挂起,无法执行后续run方法。
- 如果run方法中抛出错误,会导致容器失败。
- 该类问题的解决方式请参文章末尾。
在Idea中以内置Tomcat方式启动
- 实现CommandLineRunner接口的类
@Slf4j
@Component
public class SyncCommandRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
//业务逻辑处理
int i = 1;
Thread.sleep(10000);
while (i < 5){
log.debug("I am is [{}]",i++);
Thread.sleep(2000);
//抛出异常
if(i == 4){
throw new RuntimeException();
}
}
}
}
内置Tomcat启动后的效果
从上图看,可以得出结论:在内置tomcat环境启动下,容器先启动成功后,才开始执行实现CommandLineRunnerd的类,该类在处理业务过程中抛出异常,致使tomcat挂了。故该方法是由主线程负责执行的。
外置Tomcat启动后的效果
- 调整下实现CommandLineRunner接口的类的代码
@Slf4j
@Component
public class SyncCommandRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
//业务逻辑处理
int i = 1;
Thread.sleep(10000);
while (i < 5){
log.debug("I am is [{}]",i++);
Thread.sleep(2000);
//抛出异常
/*if(i == 4){
throw new RuntimeException();
}*/
}
}
}
- 效果图
从上图可以得出结论:外置tomcat情况下,在容器成功启动前会先执行实现CommandLineRunner接口的类,同样,因为该业务是由主线程去执行的,故若该类启动失败了,会导致容器启动失败。
潜在的风险
- 在外置的tomcat下,不能因为启动时的处理异常导致整个容器挂掉,这是非常不可取的。
- 可以增加一个线程去处理业务,进而不至于阻塞主线程的启动,也不会因为出错致使主线程挂掉。
尝试
- 测试代码
@Slf4j
@Component
public class SyncCommandRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
Thread.sleep(10000);
new Thread(new Runnable() {
@Override
public void run() {
//业务逻辑处理
int i = 1;
while (i < 5){
log.debug("I am is [{}]",i++);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//抛出异常
if(i == 4){
throw new RuntimeException();
}
}
}
}).start();
}
}
效果图
从图中可以得出结论:通过开启一个新线程处理业务后,不影响主线程的启动,同时,处理业务的线程报错也不会使主线程挂掉。
解决方式
- 方法: 在实现CommandLineRunner类中新开启一个线程处理业务。
以为可以结果不行的方法
在spring 3.x之后,就已经内置了@Async注解来处理异步调用的问题,故在实现CommandLineRunner类中的run()方法前加上一个异步注解即可。Spring中@Async
例如如下代码:
@Slf4j
@Component
public class SyncCommandRunner implements CommandLineRunner {
@Async
@Override
public void run(String... args) throws Exception {
int i = 1;
Thread.sleep(10000);
while (i < 5){
//业务逻辑处理
log.debug("I am is [{}]",i++);
Thread.sleep(2000);
}
}
}
经过测试发现此种方法并不可以。可能是@Async注解的实现机制的原因。
异步处理可能产生的问题
如果大家做了实际测试,可以发现,当采用线程执行业务还未结束时,在idea上点击一次关闭项目,发现容器关闭,但是发现并没有还未完全关闭,需要再点一次。这个后续点击的次数取决于未结束的线程有多少。
可以采用的解决方式是实现ServletContextListener,该监听器中有如下两个方法:
@Override
public void contextInitialized(ServletContextEvent sce) {
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
//处理操作:将未接受的线程关闭或者采用其他手段让线程结束
}