Spring Boot 2 实战:自定义启动运行逻辑

1. 前言

不知道你有没有接到这种需求,项目启动后立马执行一些逻辑。比如缓存预热,或者上线后的广播之类等等。可能现在没有但是将来会有的。想想你可能的操作, 写个接口上线我调一次行吗?NO!NO!NO!这种初级菜鸟才干的事。今天告诉你个骚操作使得你的代码更加优雅,逼格更高。

2. CommandLineRunner 接口

 package org.springframework.boot;
 
 import org.springframework.core.Ordered;
 import org.springframework.core.annotation.Order;
 
 /**
  * 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
  * @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;
 
 }

CommandLineRunner 作用是当springApplication 启动后,在同一应用上下文中定义的多个 CommandLineRunner 类型的 Spring Bean 按照标记顺序执行。如果你想替代以数组方式接收 args 参数 可以用 另一个接口代替 org.springframework.boot.ApplicationRunner

talk is cheap show your code 下面我就来操作一波演示一下。

2.1 优先级比较高的 CommandLineRunner 实现

 package cn.felord.begin;
 
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.core.Ordered;
 import org.springframework.stereotype.Component;
 
 /**
  * 优先级比较高 通过实现接口{@link Ordered}的方式 来指定优先级
  *  命令行测试参数     --foo=bar --dev.name=码农小胖哥  java,springboot
  * @author Felordcn
  * @since 2019/6/17 23:06
  */
 @Slf4j
 @Component
 public class HighOrderCommandLineRunner implements CommandLineRunner , Ordered {
     @Override
     public void run(String... args) throws Exception {

       log.info("i am  highOrderRunner");
     }
 
     @Override
     public int getOrder() {
         return Ordered.HIGHEST_PRECEDENCE;
     }
 } 

2.2 优先级比较低的 CommandLineRunner 实现:

 package cn.felord.begin;
 
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.core.Ordered;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
 /**
  * 优先级比较低 通过注解{@link Order}方式来指定优先级
  * 比最优大64 说明会在 {@link HighOrderCommandLineRunner} 之后执行
  *
  * @author Felord
  * @since 2019/6/17 23:07
  */
 @Slf4j
 @Order(Ordered.HIGHEST_PRECEDENCE + 64)
 @Component
 public class LowOrderCommandLineRunner implements CommandLineRunner {
     @Override
     public void run(String... args) throws Exception {
         log.info("i am  lowOrderRunner");
     }
 }

2.3 用 ApplicationRunner 实现最低优先级:

 package cn.felord.begin;
 
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.ApplicationArguments;
 import org.springframework.boot.ApplicationRunner;
 import org.springframework.core.Ordered;
 import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
 
 import java.util.List;
 
 /**
  * 优先级最低的实现
  * @author Felordcn
  * @since 2019/6/18 22:13
  */
 @Slf4j
 @Component
 public class DefaultApplicationRunner implements ApplicationRunner, Ordered {
     @Override
     public void run(ApplicationArguments args) throws Exception {
 
         log.info("i am applicationRunner");
         
     }
 
     @Override
     public int getOrder() {
         return Ordered.HIGHEST_PRECEDENCE+65;
     }
 }

启动springboot 后控制台打印出了执行结果:

 2019-11-02 21:18:14.603  INFO 10244 --- [           main] c.f.begin.HighOrderCommandLineRunner   : i am  highOrderRunner
 2019-11-02 21:18:14.604  INFO 10244 --- [           main] c.f.begin.LowOrderCommandLineRunner    : i am  lowOrderRunner
 2019-11-02 21:18:14.604  INFO 10244 --- [           main] c.f.begin.DefaultApplicationRunner     : i am applicationRunner

3. 进阶操作 —— 读取通过Spring Boot命令行启动注入的参数

达到我们开篇的期望结果。那么这两个接口啥区别呢? Spring 官方不会吃饱了没事干弄两个这来折腾人,应该是有区别的,根据接口方法 run 方法可以看出来参数都不一样,额外科普一下 Spring Boot 如何传递额外参数通过命令行 执行 java -jar 传递给 main 方法,规则如下

  • 键值对 格式为 --K=V 多个使用空格隔开
  • 值 多个空格隔开
    在idea 开发工具中打开main方法配置项,进行如下配置,其他ide工具同理。参数内容为:

--foo=bar --dev.name=码农小胖哥 java springboot

HighOrderCommandLineRunner 打印一下 args 参数:

 package cn.felord.begin;
 
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.core.Ordered;
 import org.springframework.stereotype.Component;
 
 /**
  * 优先级比较高 通过实现接口{@link Ordered}的方式 来指定优先级
  *  命令行测试参数     --foo=bar --dev.name=码农小胖哥  java,springboot
  * @author dax
  * @since 2019/6/17 23:06
  */
 @Slf4j
 @Component
 public class HighOrderCommandLineRunner implements CommandLineRunner , Ordered {
     @Override
     public void run(String... args) throws Exception {
 
         for (String arg : args) {
             System.out.println("arg = " + arg);
         }
 
       log.info("i am  highOrderRunner");
     }
 
     @Override
     public int getOrder() {
         return Ordered.HIGHEST_PRECEDENCE;
     }
 }

然后 DefaultApplicationRunnerApplicationArguments 我们也一探究竟:

 package cn.felord.begin;
 
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.ApplicationArguments;
 import org.springframework.boot.ApplicationRunner;
 import org.springframework.core.Ordered;
 import org.springframework.stereotype.Component;
 import org.springframework.util.CollectionUtils;
 
 import java.util.List;
 
 /**
  * @author Felord
  * @since 2019/6/18 22:13
  */
 @Slf4j
 @Component
 public class DefaultApplicationRunner implements ApplicationRunner, Ordered {
     @Override
     public void run(ApplicationArguments args) throws Exception {
 
         log.info("i am applicationRunner");
 
         args.getOptionNames().forEach(System.out::println);
         System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>");
         String[] sourceArgs = args.getSourceArgs();
 
         if (sourceArgs!=null){
             for (String sourceArg : sourceArgs) {
                 System.out.println("sourceArg = " + sourceArg);
             }
         }
 
 
         System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
         List<String> foo = args.getOptionValues("foo");
         if (!CollectionUtils.isEmpty(foo)){
             foo.forEach(System.out::println);
         }
 
         System.out.println("++++++++++++");
         List<String> nonOptionArgs = args.getNonOptionArgs();
         System.out.println("nonOptionArgs.size() = " + nonOptionArgs.size());
         nonOptionArgs.forEach(System.out::println);
     }
 
     @Override
     public int getOrder() {
         return Ordered.HIGHEST_PRECEDENCE+65;
     }
 }

重新启动 Spring Boot 控制台打印出了结果:

 
 arg = --foo=bar
 arg = --dev.name=码农小胖哥
 arg = java
 arg = springboot
 2019-11-02 21:18:14.603  INFO 10244 --- [           main] c.f.begin.HighOrderCommandLineRunner   : i am  highOrderRunner
 2019-11-02 21:18:14.604  INFO 10244 --- [           main] c.f.begin.LowOrderCommandLineRunner    : i am  lowOrderRunner
 2019-11-02 21:18:14.604  INFO 10244 --- [           main] c.f.begin.DefaultApplicationRunner     : i am applicationRunner
 dev.name
 foo
 >>>>>>>>>>>>>>>>>>>>>>>>>>
 sourceArg = --foo=bar
 sourceArg = --dev.name=码农小胖哥
 sourceArg = java
 sourceArg = springboot
 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 bar
 ++++++++++++
 nonOptionArgs.size() = 2
 java
 springboot

我们发现可以利用这两个接口来读取 Spring Boot 命令行参数。 其实我们还可以使用 @Value 注解来读取,这里不进行讲解,有兴趣可以自己尝试。 到这里 ApplicationRunnerCommandLineRunner 的区别从控制台我们就很了然了。

4. ApplicationRunnerCommandLineRunner 的区别

从上面的 log 我们知道 arg=CommandLineRunnerargs数组打印,仅仅单纯把上面的参数以空格为规则解析成了原汁原味的数组。而 ApplicationRunner 则更加精细化。通过打印可以知道 ApplicationArguments 提供了一些很有用的参数解析方法:

  • args.getOptionNames() 是获取键值对 --K=V 中的 K
  • args.getOptionValues("foo") 用来通过 K 来获取键值对的值 V
  • args.getSourceArgs() 等同于 CommandLineRunnerargs 数组
  • args.getNonOptionArgs() 最惨用来获取单身狗

要注意的是 解析 ApplicationArguments 时要处理空指针问题。

5. 总结

今天我们通过对 CommandLineRunnerApplicationRunner 讲解。解决了如何在 Spring Boot 启动时执行一些逻辑的问题以及如何来编排多个启动逻辑的优先级顺序。同时我们进阶一步,通过这两个方法读取 Spring Boot 启动项参数。进而也搞清楚了这两个接口之间的细微的区别。希望对你有所帮助。

关注公众号:码农小胖哥,获取更多资讯

个人博客:https://felord.cn

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容