美文网首页Java架构技术进阶Java面试题总结
被SpringBoot难倒的第N天,为什么我的CommandLi

被SpringBoot难倒的第N天,为什么我的CommandLi

作者: 代码小当家 | 来源:发表于2020-03-07 17:00 被阅读0次

    推荐阅读:

    Java程序猿阿谷:闭关修炼21天,“啃完”283页pdf,我终于4面拿下字节跳动offer​zhuanlan.zhihu.com

    图标 Java程序猿阿谷:47天时间,洒热血复习,我成功“挤进”了字节跳动(附Java面试题+学习笔记+算法刷题)​zhuanlan.zhihu.com 图标

    Springboot 工程中通常都有几个 CommandLineRunner 的实现类,用来在程序启动之后干点什么。但如果使用不当,可能就会发现有的 Runner 在程序启动之后执行了,有的却没有执行,更奇怪的是程序也没有报错。原因就是...

    先看现象

    假设一个工程中需要用到 3 个 CommandLineRunner, 其中 2 个 runner 需要执行一个 while(true) 循环

    代码如下:

    @Component
    public class Runner1 implements CommandLineRunner {
        public void run(String... args) throws Exception {
            log.info("runner 1 running...");
        }
    }
    
    @Component
    public class Runner2 implements CommandLineRunner {
        public void run(String... args) throws Exception {
            log.info("runner 2 running...")
            while(true) {
                doSomething();
            }
        }
    }
    
    @Component
    public class Runner3 implements CommandLineRunner {
        public void run(String... args) throws Exception {
            log.info("runner 3 running...")
            while(true) {
                doOtherThing();
            }
        }
    }
    

    程序运行之后 你会发现 Runner2 和 Runnner3 总会有一个不运行,甚至 Runner1 也不运行,但是程序也不报错,怎么回事?

    透过本质看现象

    是的,先看 CommandLineRunner 是怎么执行的才能知道错在了哪里从而解决这个问题。

    CommandLineRunner.java

    /**
     * Interface used to indicate that a bean should <em>run</em> when it is contained within
     * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
     * within the same application context and can be ordered using the {@link Ordered}
     * interface or {@link Order @Order} annotation.
     * <p>
     * If you need access to {@link ApplicationArguments} instead of the raw String array
     * consider using {@link ApplicationRunner}.
     *
     * @author Dave Syer
     * @since 1.0.0
     * @see ApplicationRunner
     */
    @FunctionalInterface
    public interface CommandLineRunner {
    
        /**
         * Callback used to run the bean.
         * @param args incoming main method arguments
         * @throws Exception on error
         */
        void run(String... args) throws Exception;
    
    }
    

    注释第一句话表示:实现了这个接口的一个 spring bean 在程序启动之后应该 run.

    这里的 run 用了 <em> 标签进行了强调,为什么?不着急回答,再往下看 看看 CommandLineRunner 是如何被Spring Boot 执行的...

    SpringApplication.java

    接下来看看 SpringApplication 这个类的注释信息:

    * Class that can be used to bootstrap and launch a Spring application from a Java main
     * method. By default class will perform the following steps to bootstrap your
     * application:
     *
     * <ul>
     * <li>Create an appropriate {@link ApplicationContext} instance (depending on your
     * classpath)</li>
     * <li>Register a {@link CommandLinePropertySource} to expose command line arguments as
     * Spring properties</li>
     * <li>Refresh the application context, loading all singleton beans</li>
     * <li>Trigger any {@link CommandLineRunner} beans</li>
     * </ul>
    

    从这个注释可以知道,SpringApplication 负责触发所有 CommandLineRunner 的运行。到代码部分,就是下面的几个方法:

    • callRunners()
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
            List<Object> runners = new ArrayList<>();
            runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
            runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
            AnnotationAwareOrderComparator.sort(runners);
            for (Object runner : new LinkedHashSet<>(runners)) {
                if (runner instanceof ApplicationRunner) {
                    callRunner((ApplicationRunner) runner, args);
                }
                if (runner instanceof CommandLineRunner) {
                    callRunner((CommandLineRunner) runner, args);
                }
            }
        }
    
    • callRunner()
    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);
            }
        }
    

    callRunners 负责从上下文中获取到所有 CommandLineRunner 的实现类,循环遍历,对于每一个实现类对象,调用 callRunner 方法进行触发。而在 callRunner 方法中同步执行的是 runner 对象的 run 方法.

    到此,答案已经很清晰了,CommandLineRunner 不 run ,或者有的 run 有的不 run,程序也不报错的原因就是:

    1. 你有多个 CommandLineRunner 的实现类
    2. 在某个实现类的 run 方法体中调用了同步阻塞的API或者是一个 while(true) 循环

    这样就导致了 SpringApplication 在执行这个 run 方法时阻塞,无法继续执行循环中的下一个 runner 的 run 方法。

    为了避免程序中犯这个错,一定要记住,

    CommandLineRunner != Runnable

    CommandLineRunner != Runnable

    CommandLineRunner != Runnable

    虽然这两个接口都有一个 run 方法,很容易让人理解成一个 CommandLineRunner 就会对应一个线程。NO!

    知错就改

    对于 Runner1 不需要做任何修改。 对于 Runner2 和 Runner3,需要做以下修改,让 while(true) 循环不阻塞 run 方法的运行即可。

    @Component
    public class Runner2 implements CommandLineRunner {
        public void run(String... args) throws Exception {
            log.info("runner 2 running...")
            new Thread(() -> {
                while(true) {
                    doSomething();
                }
            }).start();
        }
    }
    
    @Component
    public class Runner3 implements CommandLineRunner {
        public void run(String... args) throws Exception {
            log.info("runner 3 running...")
            new Thread(() -> {
                while(true) {
                    doOtherThing();
                }
            }).start();
        }
    }
    

    作者:jwenjian
    链接:https://juejin.im/post/5e6205b651882549215ce3a9
    来源:掘金

    相关文章

      网友评论

        本文标题:被SpringBoot难倒的第N天,为什么我的CommandLi

        本文链接:https://www.haomeiwen.com/subject/epocdhtx.html