it编程 > 编程语言 > Java

SpringBoot 中 CommandLineRunner的作用示例详解

9人参与 2025-06-10 Java

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的相关方法:

可以通过下面的命令运行一个 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()方法,就可以看到资源加载逻辑。

到此这篇关于springboot 中 commandlinerunner的作用的文章就介绍到这了,更多相关springboot 中 commandlinerunner内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

您想发表意见!!点此发布评论

推荐阅读

Java死锁问题解决方案及示例详解

06-10

Java中的try-catch块和异常捕获方式

06-10

Java中的内存模型(JMM)和锁机制详解

06-10

java并发中的同步器使用方式

06-10

Java使用Spring AI的10个实用技巧分享

06-10

Java IO流必备之File、递归与字符集举例详解

06-10

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论