美文网首页
13.SpringShell自定义Runner-后台运行单条命令

13.SpringShell自定义Runner-后台运行单条命令

作者: Java扫地僧 | 来源:发表于2019-01-30 11:28 被阅读0次

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

  1. 自定义Input, 实现Input接口, InputProvider.readInput()方法需要返回一个Input类型的值
  2. 自定义InputProvider, 实现InputProvider接口, Shell.run()方法会调用InputProvider.readInput()获取执行名
  3. 自定义ApplicationRunner, 实现ApplicationRunner接口, 因为容器初始化完成之后会执行所有ApplicationRunner 的run方法
  4. 设定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:>

相关文章

网友评论

      本文标题:13.SpringShell自定义Runner-后台运行单条命令

      本文链接:https://www.haomeiwen.com/subject/uytxsqtx.html