13.SpringShell自定义Runner-后台运行单条命令
2019-01-30 本文已影响0人
Java扫地僧
SpringShell 应用启动时, 会自动创建两个ApplicationRunner组件: ScriptShellApplicationRunner 和 InteractiveShellApplicationRunner, 其中ScriptShellApplicationRunner 用来支持启动直接运行脚本方式, InteractiveShellApplicationRunner 用来支持交互式启动方式. 当我们需要新增一种运行方式时, 那么可以通过自定义ApplicationRunner 实现. 深入了解ScriptShellApplicationRunner 和 InteractiveShellApplicationRunner的逻辑, 可阅读笔者的下一篇博客.
1. SpringShell应用默认运行方式
1.1 交互式运行
$ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar
# 省略启动信息...
shell:>add 2 3
5
shell:>exit
1.2 脚本运行
$ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar
# 省略启动信息...
5
2
4
1.3 笔者期望新增运行方式
$ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar
# 省略启动信息...
3
7
2. 自定义ApplicationRunner
- 自定义Input, 实现Input接口, InputProvider.readInput()方法需要返回一个Input类型的值
- 自定义InputProvider, 实现InputProvider接口, Shell.run()方法会调用InputProvider.readInput()获取执行名
- 自定义ApplicationRunner, 实现ApplicationRunner接口, 因为容器初始化完成之后会执行所有ApplicationRunner 的run方法
- 设定ApplicationRunner的优先级, 笔者设定位于默认两个ApplicationRunner 直接, 这个很关键, 直接影响核心代码的编写.
2.1 自定义Input
SpringShell 源码提供了ParsedLineInput, 但是类权限为包级别, 笔者访问不到. 因此复制源码自成一类.
/**
* @Description: 自定义行解析, 复制的底层代码: org.springframework.shell.jline.ParsedLineInput
* @author: zongf
* @date: 2019-01-28 11:18
*/
class MyParsedLineInput implements Input {
private final ParsedLine parsedLine;
MyParsedLineInput(ParsedLine parsedLine) {
this.parsedLine = parsedLine;
}
@Override
public String rawText() {
return parsedLine.line();
}
@Override
public List<String> words() {
return sanitizeInput(parsedLine.words());
}
static List<String> sanitizeInput(List<String> words) {
words = words.stream()
.map(s -> s.replaceAll("^\\n+|\\n+$", ""))
.map(s -> s.replaceAll("\\n+", " "))
.collect(Collectors.toList());
return words;
}
}
2.2 自定义InputProvider
- Shell.run 方法会循环调用InputProvider的readInput()方法, 当readInput()方法返回为null时, 终止循环
- 笔者使用Queue结构来存储启动参数中所有的命令, 执行一条命令移除一条命令, 命令执行完之后返回null.
/**
* @Description: 读取命令
* @author: zongf
* @date: 2019-01-28 11:19
*/
class MyInputProvider implements InputProvider {
// 存储要执行的所有命令
private Queue<String> commands;
private final Parser parser;
public MyInputProvider(Parser parser, Queue<String> commands) {
this.parser = parser;
this.commands = commands;
}
@Override
public Input readInput() {
String command = commands.poll();
if (command == null) {
// return null 时退出应用
return null;
}else {
ParsedLine parsedLine = parser.parse(command, command.length());
return new MyParsedLineInput(parsedLine);
}
}
}
2.3 自定义ApplicationRuuner
- 自定义实现ApplicationRunner 接口的实现类
- 将自定义ApplicationRunner 注册为Spring的一个组件, 即使用@Component修饰
- 设置自定义Runner运行顺序, 保证运行顺序为: ScriptShellApplicationRunner > 自定义Runner > InteractiveShellApplicationRunner, 使用@Order 限制. 只有这样能让脚本参数和命令参数共存.
- 延迟注入内置属性: Parser, Shell, enviroment
/**
* @Description: 自定义命令Runner, 启动应用后直接执行多条命令, 执行后结束
* @author: zongf
* @date: 2019-01-28 09:58
*/
@Component
@Order(InteractiveShellApplicationRunner.PRECEDENCE - 50) // order 越小, 越先执行
public class MyCommandsRunner implements ApplicationRunner {
@Lazy
@Autowired
private Parser parser;
@Lazy
@Autowired
private Shell shell;
@Lazy
@Autowired
private ConfigurableEnvironment environment;
@Override
public void run(ApplicationArguments args) throws Exception {
//过滤掉所有@开头的参数, @开头的参数交给ScriptShellApplicationRunner 处理
List<String> cmds = args.getNonOptionArgs().stream()
.filter(s -> !s.startsWith("@"))
.collect(Collectors.toList());
//如果启动参数中, 命令不为空, 则执行
if (cmds != null && cmds.size() > 0) {
// 关闭交互式应用: 确保关闭应用后, 直接退出应用, 不停留在交互式窗口中
InteractiveShellApplicationRunner.disable(environment);
// 将所有命令存储到队列中
Queue<String> queue = new PriorityQueue<>();
queue.addAll(cmds);
// 执行命令
shell.run(new MyInputProvider(parser, queue));
}
}
}
3. 测试
新增自定义ApplicationRunner 之后, SpringShell应用便有了四种运行方式
3.1 测试直接运行命令
$ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar "add 2 3"
Welcom to :
__ _____ _______ __ __________ __
/ |/ /\ \/ / ___// / / / ____/ / / /
/ /|_/ / \ /\__ \/ /_/ / __/ / / / /
/ / / / / /___/ / __ / /___/ /___/ /___
/_/ /_/ /_//____/_/ /_/_____/_____/_____/
Version: 0.0.1-SNAPSHOT
Author: zongf
Date: 2019-01-26
# 应用启动之后, 直接执行了add 命令, 命令执行之后, 退出应用程序
5
3.2 测试直接运行脚本
$ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar @/tmp/zongf/script
Welcom to :
__ _____ _______ __ __________ __
/ |/ /\ \/ / ___// / / / ____/ / / /
/ /|_/ / \ /\__ \/ /_/ / __/ / / / /
/ / / / / /___/ / __ / /___/ /___/ /___
/_/ /_/ /_//____/_/ /_/_____/_____/_____/
Version: 0.0.1-SNAPSHOT
Author: zongf
Date: 2019-01-26
# 应用启动之后, 直接运行脚本中定义的命令
5
2
4
3.3 测试混合运行
需要注意的是, 会先执行所有的脚本,从才会执行所有命令. 因为ScriptShellApplicationRunner 的优先级在自定义Runner优先级之前.
$ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar @/tmp/zongf/script "add 20 30" @/tmp/zongf/script
Welcom to :
__ _____ _______ __ __________ __
/ |/ /\ \/ / ___// / / / ____/ / / /
/ /|_/ / \ /\__ \/ /_/ / __/ / / / /
/ / / / / /___/ / __ / /___/ /___/ /___
/_/ /_/ /_//____/_/ /_/_____/_____/_____/
Version: 0.0.1-SNAPSHOT
Author: zongf
Date: 2019-01-26
# 执行第一个脚本
5
2
4
# 执行第二个脚本
5
2
4
# 执行自定义命令
50
3.4 测试交互式运行
zongf@zongf-E570 spring-shell $ java -jar ./target/spring-shell-0.0.1-SNAPSHOT.jar
Welcom to :
__ _____ _______ __ __________ __
/ |/ /\ \/ / ___// / / / ____/ / / /
/ /|_/ / \ /\__ \/ /_/ / __/ / / / /
/ / / / / /___/ / __ / /___/ /___/ /___
/_/ /_/ /_//____/_/ /_/_____/_____/_____/
Version: 0.0.1-SNAPSHOT
Author: zongf
Date: 2019-01-26
# 应用启动之后, 处于交互式窗口之中
shell:>add 2 3
5
shell:>