在项目中发现,服务刚发布完成时,接口rt会有明显上升,就考虑到使用资源预热,在服务发布完成,正式流量进来前(通过健康检查来设置流量进入的时机)模拟线上的请求,先行访问数据库和缓存,确保相关的线程池创建完成。
根据这个方案,因为项目是springboot,所以直接google,发现通过CommandLineRunner和ApplicationRunner可以实现该功能。
先总体介绍下两个接口的功能,然后以代码来看看具体实现。
- 这两个接口都有一个run()方法,在实现接口时需要覆盖该方法,并使用@Component注解使其成为bean。
- CommandLineRunner和ApplicationRunner的作用是相同的。不同之处在于CommandLineRunner接口的run()方法接收String数组作为参数,而ApplicationRunner接口的run()方法接收ApplicationArguments对象作为参数。
- 当程序启动时,我们传给main()方法的参数可以被实现CommandLineRunner和ApplicationRunner接口的类的run()方法访问。
- 我们可以创建多个实现CommandLineRunner和ApplicationRunner接口的类。为了使他们按一定顺序执行,可以使用@Order注解或实现Ordered接口。
- CommandLineRunner和ApplicationRunner接口的run()方法在SpringApplication完成启动时执行。启动完成之后,应用开始运行。CommandLineRunner和ApplicationRunner的作用是在程序开始运行前执行任务或记录信息。
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 。
优先级比较高的 CommandLineRunner 实现
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/**
* 优先级比较高 通过实现接口{@link Ordered}的方式 来指定优先级
*/
@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;
}
}
优先级比较低的 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} 之后执行
*
*/
@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");
}
}
用 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;
/**
* 优先级最低的实现
*/
@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;
}
}
读取通过Spring Boot命令行启动注入的参数
两个接口的主要区别是参数处理不一致, 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
*/
@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;
}
}
然后 DefaultApplicationRunner
的 ApplicationArguments
我们也一探究竟:
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
注解来读取。 到这里 ApplicationRunner
与 CommandLineRunner
的区别从控制台我们就很了然了。
ApplicationRunner
与 CommandLineRunner
的区别
从上面的 log 我们知道 arg=
为 CommandLineRunner
的 args
数组打印,仅仅单纯把上面的参数以空格为规则解析成了原汁原味的数组。而 ApplicationRunner
则更加精细化。通过打印可以知道 ApplicationArguments
提供了一些很有用的参数解析方法:
-
args.getOptionNames()
是获取键值对--K=V
中的K
-
args.getOptionValues("foo")
用来通过K
来获取键值对的值V
-
args.getSourceArgs()
等同于CommandLineRunner
的args
数组 -
args.getNonOptionArgs()
最惨用来获取单身狗
要注意的是 解析 ApplicationArguments 时要处理空指针问题。
总结
今天我们通过对 CommandLineRunner
和 ApplicationRunner
讲解。解决了如何在 Spring Boot 启动时执行一些逻辑的问题以及如何来编排多个启动逻辑的优先级顺序。同时我们进阶一步,通过这两个方法读取 Spring Boot 启动项参数。进而也搞清楚了这两个接口之间的细微的区别。
参考链接:
https://juejin.im/post/5dbd9728f265da4d45769c73
https://www.jianshu.com/p/de7b0e124248
//最好是放在网关统一去做,让流量逐渐增加
如何去做资源预热
网友评论