Spring Boot中实现CommandLineRunner完成启动时加载数据在内外置tomcat条件下的启动差异及正确的使用姿势

结论

  • 首先先将结论奉上
  1. 实现CommandLineRunner接口的类,需要实现run方法,项目启动时,会由主线程去执行实现了CommandLineRunner接口类的run方法。
  2. 如果优先执行的run中有阻塞方法,会导致线程挂起,无法执行后续run方法。
  3. 如果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启动后的效果

Spring Boot中实现CommandLineRunner完成启动时加载数据在内外置tomcat条件下的启动差异及正确的使用姿势_第1张图片

从上图看,可以得出结论:在内置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();
            }*/
        }
    }
}
  • 效果图
    Spring Boot中实现CommandLineRunner完成启动时加载数据在内外置tomcat条件下的启动差异及正确的使用姿势_第2张图片

从上图可以得出结论:外置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();
        
    }
}

效果图

Spring Boot中实现CommandLineRunner完成启动时加载数据在内外置tomcat条件下的启动差异及正确的使用姿势_第3张图片

从图中可以得出结论:通过开启一个新线程处理业务后,不影响主线程的启动,同时,处理业务的线程报错也不会使主线程挂掉。

解决方式

  • 方法: 在实现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) {
        //处理操作:将未接受的线程关闭或者采用其他手段让线程结束
    }

你可能感兴趣的:(Spring,Boot,Spring,Boot)