【Java开发日记】说一说 SpringBoot 中 CommandLineRunner

news2025/6/8 13:30:26

目录

1、CommandLineRunner

SpringBoot中CommandLineRunner的作用

简单例子

多个类实现CommandLineRunner接口执行顺序的保证

通过实现Ordered接口实现控制执行顺序

通过@Order注解实现控制执行顺序

@Order 作用

2、ApplicationRunner

3、传递参数

4、源码跟踪

run()方法

callRunners方法


1、CommandLineRunner

SpringBoot中CommandLineRunner的作用

平常开发中有可能需要实现在项目启动后执行的功能,SpringBoot提供的一种简单的实现方案就是添加一个model并实现CommandLineRunner接口,实现功能的代码放在实现的run方法中。也就是项目一启动之后,就立即需要执行的动作。只需要在项目里面简单的配置,就可以实现这个功能。 

简单例子
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyStartupRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("项目已经启动");
    }
}

多个类实现CommandLineRunner接口执行顺序的保证

通过实现Ordered接口实现控制执行顺序
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/**
 * 优先级最高
 * 该类期望在springboot 启动后第一顺位执行
 * @since 12:57
 **/
@Slf4j
@Component
public class HighOrderCommandLineRunner implements CommandLineRunner, Ordered {
    @Override
    public void run(String... args) throws Exception {
        for (String arg : args) {
            log.info("arg = " + arg);
        }
        log.info("i am highOrderRunner");
    }
    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+1;
    }
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/**
 * 优先级低于{@code HighOrderCommandLineRunner}
 * @since 12:59
 **/
@Slf4j
@Component
public class LowOrderCommandLineRunner implements CommandLineRunner, Ordered {
    @Override
    public void run(String... args) throws Exception {
        log.info("i am lowOrderRunner");
    }
    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+1;
    }
}

启动Spring Boot程序后,控制台按照预定的顺序打印出了结果:

2020-05-30 23:11:03.685  INFO 11976 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-30 23:11:03.701  INFO 11976 --- [           main] c.f.Application  : Started SpringBootApplication in 4.272 seconds (JVM running for 6.316)
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.LowOrderCommandLineRunner   : i am lowOrderRunner

通过@Order注解实现控制执行顺序

SpringBoot在项目启动后会遍历所有实现CommandLineRunner的实体类并执行run方法,如果需要按照一定的顺序去执行,那么就需要在实体类上使用一个@Order注解(或者实现Order接口)来表明顺序

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value=2)
public class MyStartupRunner1 implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("执行2");
    }
}
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value=1)
public class MyStartupRunner2 implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("执行1");
    }
}

控制台显示

执行1
执行2

根据控制台结果可判断,@Order 注解的执行优先级是按value值从小到大顺序。

@Order 作用

项目启动之后,要执行的动作是比较的多,那么到底先执行哪个,那么就可以利用这个注解限定优先级。 :::danger Ordered接口并不能被 @Order注解所代替。

2、ApplicationRunner

在Spring Boot 1.3.0又引入了一个和CommandLineRunner功能一样的接口ApplicationRunnerCommandLineRunner接收可变参数String... args,而ApplicationRunner 接收一个封装好的对象参数ApplicationArguments。除此之外它们功能完全一样,甚至连方法名都一样。声明一个ApplicationRunner并让它优先级最低:

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 java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
 * 优先级最低
 **/
@Slf4j
@Component
public class DefaultApplicationRunner implements ApplicationRunner, Ordered {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("i am applicationRunner");
        Set<String> optionNames = args.getOptionNames();
        log.info("optionNames = " + optionNames);
        String[] sourceArgs = args.getSourceArgs();
        log.info("sourceArgs = " + Arrays.toString(sourceArgs));
        List<String> nonOptionArgs = args.getNonOptionArgs();
        log.info("nonOptionArgs = " + nonOptionArgs);
        List<String> optionValues = args.getOptionValues("foo");
        log.info("optionValues = " + optionValues);
    }
    @Override
    public int getOrder() {
        return Integer.MIN_VALUE+2;
    }
}

按照顺序打印了三个类的执行结果:

2020-06-01 13:02:39.420  INFO 19032 --- [           main] c.f.MybatisResultmapApplication  : Started MybatisResultmapApplication in 1.801 seconds (JVM running for 2.266)
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionNames = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionValues = null
 optionValues = null

Ordered接口并不能被 @Order注解所代替。

3、传递参数

Spring Boot应用启动时是可以接受参数的,换句话说也就是Spring Bootmain方法是可以接受参数的。这些参数通过命令行 java -jar yourapp.jar 来传递。CommandLineRunner会原封不动照单全收这些接口,这些参数也可以封装到ApplicationArguments对象中供ApplicationRunner调用。看一下ApplicationArguments的相关方法:

  • getSourceArgs() 被传递给应用程序的原始参数,返回这些参数的字符串数组。
  • getOptionNames() 获取选项名称的Set字符串集合。如 --spring.profiles.active=dev --debug 将返回["spring.profiles.active","debug"] 。
  • getOptionValues(String name) 通过名称来获取该名称对应的选项值。如--foo=bar --foo=baz 将返回["bar","baz"]
  • containsOption(String name) 用来判断是否包含某个选项的名称。
  • getNonOptionArgs() 用来获取所有的无选项参数。

可以通过下面的命令运行一个 Spring Boot应用 Jar

java -jar yourapp.jar --foo=bar --foo=baz --dev.name=fcant java fcantcn

或者在IDEA开发工具中打开Spring Boot应用main方法的配置项,进行命令行参数的配置,其他IDE工具同理。
运行Spring Boot应用,将会打印出:

2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=bar
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=baz
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --dev.name=fcant
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = java
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = fcantcn
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionNames = [dev.name, foo]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = [--foo=bar, --foo=baz, --dev.name=fcant, java, fcantcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = [java, fcantcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionValues = [bar, baz]

然后就可以根据实际需要动态地执行一些逻辑。 

4、源码跟踪

通过源码理解一下底层实现。 

run()方法

跟进run方法后,一路F6直达以下方法

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   //设置线程启动计时器
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   //配置系统属性:默认缺失外部显示屏等允许启动
   configureHeadlessProperty();
   //获取并启动事件监听器,如果项目中没有其他监听器,则默认只有EventPublishingRunListener
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //将事件广播给listeners
   listeners.starting();
   try {
       //对于实现ApplicationRunner接口,用户设置ApplicationArguments参数进行封装
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      //配置运行环境:例如激活应用***.yml配置文件      
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      configureIgnoreBeanInfo(environment);
      //加载配置的banner(gif,txt...),即控制台图样
      Banner printedBanner = printBanner(environment);
      //创建上下文对象,并实例化
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      //配置SPring容器      
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
      //刷新Spring上下文,创建bean过程中      
      refreshContext(context);
      //空方法,子类实现
      afterRefresh(context, applicationArguments);
      //停止计时器:计算线程启动共用时间
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      //停止事件监听器
      listeners.started(context);
      //开始加载资源
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, listeners, exceptionReporters, ex);
      throw new IllegalStateException(ex);
   }
   listeners.running(context);
   return context;
}

主要是熟悉SpringBoot的CommandLineRunner接口实现原理。因此上面SpringBoot启动过程方法不做过多介绍。直接进入CallRunners()方法内部。 

callRunners方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {
    //将实现ApplicationRunner和CommandLineRunner接口的类,存储到集合中
   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);
      }
   }
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
   try {
       //调用各个实现类中的逻辑实现
      (runner).run(args.getSourceArgs());
   }
   catch (Exception ex) {
      throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
   }
}

到此结束,再跟进run()方法,就可以看到资源加载逻辑。

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2404184.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全面理解 Linux 内核性能问题:分类、实战与调优策略

在 Linux 系统&#xff08;特别是嵌入式或服务器环境&#xff09;中&#xff0c;性能问题往往错综复杂、表象多变。只有对常见性能问题进行系统归类、理解其症状与根源&#xff0c;才能有效定位和解决。本文将围绕八大类核心性能问题&#xff0c;结合实战示例&#xff0c;逐类分…

算法-多条件排序

1、数对排序的使用 pair<ll,ll> a[31];//cmp为比较规则 ll cmp(pair<ll,ll>a,pair<ll,ll>b){if(a.first!b.first)return a.first>b.first;else return a.second<b.second; }//按照比较规则进行排序 sort(a1,a31,cmp); 2、具体例题 输入样例&#xff1…

固定ip和非固定ip的区别是什么?如何固定ip地址

在互联网中&#xff0c;我们常会接触到固定IP和非固定IP的概念。它们究竟有何不同&#xff1f;如何固定IP地址&#xff1f;让我们一起来探究这个问题。 一、固定IP和非固定IP的区别是什么 固定IP&#xff08;静态IP&#xff09;和非固定IP&#xff08;动态IP&#xff09;是两种…

使用矩阵乘法+线段树解决区间历史和问题的一种通用解法

文章目录 前言P8868 [NOIP2022] 比赛CF1824DP9990/2020 ICPC EcFinal G 前言 一般解决普通的区间历史和&#xff0c;只需要定义辅助 c h s − t ⋅ a chs-t\cdot a chs−t⋅a&#xff0c; h s hs hs是历史和&#xff0c; a a a是区间和&#xff0c; t t t是时间戳&#xff0c…

如何从浏览器中导出网站证书

以导出 GitHub 证书为例&#xff0c;点击 小锁 点击 导出 注意&#xff1a;这里需要根据你想要证书格式手动加上后缀名&#xff0c;我的是加 .crt 双击文件打开

低功耗MQTT物联网架构Java实现揭秘

文章目录 一、引言二、相关技术概述2.1 物联网概述2.2 MQTT协议java三、基于MQTT的Iot物联网架构设计3.1 架构总体设计3.2 MQTT代理服务器选择3.3 物联网设备设计3.4 应用服务器设计四、基于MQTT的Iot物联网架构的Java实现4.1 开发环境搭建4.2 MQTT客户端实现4.3 应用服务器实现…

ideal2022.3.1版本编译项目报java: OutOfMemoryError: insufficient memory

最近换了新电脑&#xff0c;用新电脑拉项目配置后&#xff0c;启动时报错&#xff0c;错误描述 idea 启动Springboot项目在编译阶段报错&#xff1a;java: OutOfMemoryError: insufficient memory 2. 处理方案 修改VM参数&#xff0c;分配更多内存 ❌ 刚刚开始以为时JVM内存设置…

centos7编译安装LNMP架构

一、LNMP概念 LNMP架构是一种常见的网站服务器架构&#xff0c;由Linux操作系统、Nginx Web服务器、MySQL数据库和PHP后端脚本语言组成。 1 用户请求&#xff1a;用户通过浏览器输入网址&#xff0c;请求发送到Nginx Web服务器。 2 Nginx处理&#xff1a;Nginx接收请求后&…

Spring Boot 3.3 + MyBatis 基础教程:从入门到实践

Spring Boot 3.3 MyBatis 基础教程&#xff1a;从入门到实践 在当今的Java开发领域&#xff0c;Spring Boot和MyBatis是构建高效、可维护的后端应用的两个强大工具。Spring Boot简化了Spring应用的初始搭建和开发过程&#xff0c;而MyBatis则提供了一种灵活的ORM&#xff08;…

征文投稿:如何写一份实用的技术文档?——以软件配置为例

&#x1f4dd; 征文投稿&#xff1a;如何写一份实用的技术文档&#xff1f;——以软件配置为例 目录 [TOC](目录)&#x1f9ed; 技术文档是通往成功的“说明书”&#x1f4a1; 一、明确目标读者&#xff1a;他们需要什么&#xff1f;&#x1f4cb; 二、结构清晰&#xff1a;让读…

tensorflow image_dataset_from_directory 训练数据集构建

以数据集 https://www.kaggle.com/datasets/vipoooool/new-plant-diseases-dataset 为例 目录结构 训练图像数据集要求&#xff1a; 主目录下包含多个子目录&#xff0c;每个子目录代表一个类别。每个子目录中存储属于该类别的图像文件。 例如 main_directory/ ...cat/ ...…

GOOUUU ESP32-S3-CAM 果云科技开发板开发指南(一)(超详细!)Vscode+espidf 通过摄像头拍摄照片并存取到SD卡中,文末附源码

看到最近好玩的开源项目比较多&#xff0c;就想要学习一下esp32的开发&#xff0c;目前使用比较多的ide基本上是arduino、esp-idf和platformio&#xff0c;前者编译比较慢&#xff0c;后两者看到开源大佬的项目做的比较多&#xff0c;所以主要学习后两者。 本次使用的硬件是GO…

全流程开源!高德3D贴图生成系统,白模一键生成真实感纹理贴图

导读 MVPainter 随着3D生成从几何建模迈向真实感还原&#xff0c;贴图质量正逐渐成为决定3D资产视觉表现的核心因素。我们团队自研的MVPainter系统&#xff0c;作为业内首个全流程开源的3D贴图生成方案&#xff0c;仅需一张参考图与任意白模&#xff0c;即可自动生成对齐精确…

html 滚动条滚动过快会留下边框线

滚动条滚动过快时&#xff0c;会留下边框线 但其实大部分时候是这样的&#xff0c;没有多出边框线的 滚动条滚动过快时留下边框线的问题通常与滚动条样式和滚动行为有关。这种问题可能出现在使用了自定义滚动条样式的情况下。 注意&#xff1a;使用方法 6 好使&#xff0c;其它…

数据通信与计算机网络——数据与信号

主要内容 模拟与数字 周期模拟信号 数字信号 传输减损 数据速率限制 性能 注&#xff1a;数据必须被转换成电磁信号才能进行传输。 一、模拟与数字 数据以及表示数据的信号可以使用模拟或者数字的形式。数据可以是模拟的也可以是数字的&#xff0c;模拟数据是连续的采用…

【LLM大模型技术专题】「入门到精通系列教程」LangChain4j与Spring Boot集成开发实战指南

LangChain4j和SpringBoot入门指南 LangChain4jLangchain4j API语言模型消息类型内存对象ChatMemory接口的主要实现设置 API 密钥SpringBoot Configuration配置ChatLanguageModelStreamingChatLanguageModel初始化ChatModel对象模型配置分析介绍说明通过JavaConfig创建ChatModel…

Vue3 GSAP动画库绑定滚动条视差效果 绑定滚动条 滚动条动画 时间轴

介绍 GSAP 用于创建高性能、可控制的动画效果。由 GreenSock 团队开发&#xff0c;旨在提供流畅、快速、稳定的动画效果&#xff0c;并且兼容各种浏览器。 提供了多个插件&#xff0c;扩展了动画的功能&#xff0c;如 ScrollTrigger&#xff08;滚动触发动画&#xff09;、Dra…

grafana-mcp-analyzer:基于 MCP 的轻量 AI 分析监控图表的运维神器!

还在深夜盯着 Grafana 图表手动排查问题&#xff1f;今天推荐一个让 AI 能“读图说话”的开源神器 —— grafana-mcp-analyzer。 想象一下这样的场景&#xff1a; 凌晨3点&#xff0c;服务器告警响起。。。你睁着惺忪的眼睛盯着复杂的监控图表 &#x1f635;‍&#x1f4ab;花…

【题解-洛谷】B3622 枚举子集(递归实现指数型枚举)

题目&#xff1a;B3622 枚举子集&#xff08;递归实现指数型枚举&#xff09; 题目描述 今有 n n n 位同学&#xff0c;可以从中选出任意名同学参加合唱。 请输出所有可能的选择方案。 输入格式 仅一行&#xff0c;一个正整数 n n n。 输出格式 若干行&#xff0c;每行…

(LeetCode 每日一题)3170. 删除星号以后字典序最小的字符串(贪心+栈)

题目&#xff1a;3170. 删除星号以后字典序最小的字符串 思路&#xff1a;贪心栈&#xff0c;时间复杂度0(n)。 对于每一个‘ * ’&#xff0c;优先选最右边的最小字符&#xff0c;才会使最终得到的字符串最小。 用栈&#xff0c;来记录每个字符的位置下标。细节看注释。 C版本…