美文网首页
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