美文网首页程序员js css htmlJava
Spring shell 简易使用指南

Spring shell 简易使用指南

作者: AlienPaul | 来源:发表于2023-01-10 17:30 被阅读0次

前言

Java的很多开源项目中使用到了交互式命令行方式操作。交互式命令行有很多种实现方式。其中Spring shell是一种极其简单易用的交互式命令行框架。

本篇为大家带来使用Spring shell编写交互式命令行应用的简易指南。

环境准备

我们以Spring shell 2.1.1版本为例讲解Spring shell的使用方式。

Spring shell项目依赖于Springboot,它们的项目结构式完全相同的。

在Maven的pom.xml文件中加入如下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.shell</groupId>
        <artifactId>spring-shell-starter</artifactId>
        <version>2.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>${springboot.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.7.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.7.2</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

注意:Spring shell 2.x.x相对于1.x.x版本大升级之后用法改变较大。本篇不适合1.x.x版本的使用方式。

环境已准备完毕,接下来我们开始使用Spring shell。

命令编写

我们已最简单的echo命令为例,用户在echo后面输入什么就返回什么。

@ShellComponent
public class EchoCommand {
    @ShellMethod("Echo command")
    public String echo(String str) {
        return str;
    }
}

我们运行Springboot主类,可以在命令行中使用刚刚创建出的echo命令。

通常来说如果交互式命令行应用支持的命令很多,这些命令是需要归类的。归为同一类的命令在使用help命令打印帮助信息的时候会被显示在一起。归类的方式是将相关的命令定义在同一个class内。这个class需要使用@ShellComponent修饰。

@ShellComponentclass中,每一个命令参数定义和执行逻辑对应着一个@ShellMethod注解修饰的方法。

@ShellMethod注解的属性:

  • key: 自定义命令。默认为注解修饰的方法名,为数组类型,即同一个方法可以指定多个名称。如果不指定,默认命令和方法名相同。如果方法名是驼峰结构,与之对应的命令是横线分割全小写的命令方式。例如方法名为doSomething,对应的命令为do-something
  • value: 方法的描述,相当于帮助信息,可以使用help命令打印出各个方法的描述。

例如:

@ShellMethod(key = {"echo", "say"}, value = "Echo command")
public String echo(String str) {
    return str;
}

这种写法我们既可以使用echo,又可以使用say命令来调用echo方法。

传递命令参数的方式也可以修改。可以使用@ShellOption注解。它包含如下属性:

  • value: 数组类型,用来指定这个参数的key。例如@ShellOption(value = {"--parallelism", "-p"}) Integer parallelism,在调用的时候传参既可以用--parallelism 10也可以使用-p 10
  • help: 参数的帮助信息。
  • defaultValue: 参数的默认值。如果不指定默认值,则这个参数是必需(mandatory)参数。如果需要设置一个参数为可选(optional),并且不想指定默认值(默认值为null),可以使用defaultValue = ShellOption.NULL

指定命名参数

@ShellMethod(key = {"echo", "say"}, value = "Echo command")
public String echo(@ShellOption(value = {"--content", "-c"}) String str) {
    return str;
}

可以支持使用echo --content something或者echo -c something方式传入参数str。

指定默认值

@ShellMethod(key = {"echo", "say"}, value = "Echo command")
public String echo(@ShellOption(defaultValue = "something") String str) {
    return str;
}

指定了str参数默认值为something

注意:如果不指定默认值,则这个参数是必需(mandatory)参数。如果需要设置一个参数为可选(optional),并且不想指定默认值(默认值为null),可以使用defaultValue = ShellOption.NULL

参数校验

一个例子如下:

@ShellMethod("Password")
public String password(@Size(min = 6, max = 16) String pwd) {
    return pwd;
}

@Size注解可以校验参数字符串长度范围。上面的例子确保字符串长度在6到16之间。

如果参数校验失败,命令行会显示校验提示信息。校验注解自带了默认的提示信息模板,通常可以满足要求。如果需要自定义提示信息,可以添加message属性,例如:

@Size(min = 6, max = 16, message = "Length is illegal")

除了@Size之外,Spring还提供了很多其他的校验逻辑注解。具体可以查看javax.validation.constraints包下面的注解定义。

包含:

  • AssertFalse
  • AssertTrue
  • DecimalMax
  • DecimalMin
  • Digits
  • Email
  • Future
  • FutureOrPresent
  • Max
  • Min
  • Negative
  • NegativeOrZero
  • NotBlank
  • NotEmpty
  • NotNull
  • Past
  • PastOrPresent
  • Pattern
  • Positive
  • PositiveOrZero
  • Size

比如我们要求参数必须为正整数,可以这么写:

@ShellMethod("Positive")
public Integer positive(@Positive Integer integer) {
    return integer;
}

命令可用性

Spring shell交互式命令行可以支持多级命令,也就是说某些命令必须在一定的状态下才可以执行。这要求了我们能够定义命令的可用性。

我们使用一个例子来说明。代码如下:

private boolean isTestEnabled = false;

@ShellMethod("enable test")
public String enableTest() {
    isTestEnabled = true;
    return "Test enabled";
}

@ShellMethod("disable test")
public String disableTest() {
    isTestEnabled = false;
    return "Test disabled";
}

@ShellMethod("test")
public String test() {
    return "Test command";
}

public Availability testAvailability() {
    return isTestEnabled ? Availability.available() : Availability.unavailable("enable-test has not been executed");
}

这个例子中test命令默认是无法使用的。必须先执行enable-test命令启用它之后才能够调用。执行disable-test命令可以再次禁用它。

这里需要注意:判定命令是否可用的方法是testAvailability。该方法的命名规则为方法名 + Availability。方法需要返回Availability类型。如果命令不可用,需要编写一句话解释不可用原因。具体写法请见上面例子。

自定义

自定义Prompt

自定义命令提示符的方法只需要定义一个bean实现PromptProvider接口,例如:

@Component
public class MyPromptProvider implements PromptProvider {
    @Override
    public AttributedString getPrompt() {
        return new AttributedString("Hello -> ", AttributedStyle.BOLD.foreground(AttributedStyle.GREEN));
    }
}

配置上面代码后,启动spring shell应用,命令提示符会变为Hello ->

其中AttributedStyle.BOLD.foreground(AttributedStyle.GREEN)决定了命令提示符的显示风格和颜色。

getPrompt方法在每次需要显示提示符的时候都会被调用,所以说在一些复杂的场景,我们可以实现命令行处于不同状态时显示不同的提示符。

自定义banner

交互式命令应用启动的时候会打印出一些标志或者提示信息,这称之为banner。Spring shell允许我们自定义banner。

方法为保存自定义banner内容到banner.txt文件,放置在项目resources目录下。重启Springboot应用可以观察到banner修改生效。

配置

配置history log

Spring shell支持将命令执行历史记录输出为文件。配置方法如下。

编写或者修改resources/application.yaml,增加:

spring:
  shell:
    history:
      enabled: true
      name: xxx-history.log

单元测试

Spring shell的单元测试类需要添加:

@SpringBootTest(properties = {"spring.shell.interactive.enabled=false", "spring.shell.command.script.enabled=false"})

注解。

具体的编写模板如下所示:

@SpringBootTest(properties = {"spring.shell.interactive.enabled=false", "spring.shell.command.script.enabled=false"})
public class SomeTest {
    @Autowired
    private Shell shell;
    
    @Test
    public void test1() {
        // 在java代码中运行shell method的方法
        // 返回的数据类型为shell method方法返回的数据类型
        // 需要注意的是,需要判断方法返回的数据类型
        Object result = shell.evaluate(() -> "some command");
        
        if (result instanceof Throwable) {
            // 如果result为Throwable类型,说明方法执行过程中发生了异常
        }
    }
}

注意:使用上面的方式调用shell.evaluate存在一个问题,它无法解析命令行某个参数值存在空格的问题,即便参数值使用引号引起来,原因为源码中按照空格split字符串。例如someCommand --sql 'select * from table'。如果需要支持这种写法,需要自己编写Input接口的实现类,覆盖默认的words方法。如下所示:

public interface MockCommandLineInput extends Input {
    @Override
    default List<String> words() {
        if (null == rawText() || rawText().isEmpty()) {
            return Collections.emptyList();
        }
        boolean isInQuote = false;
        List<String> result = new ArrayList<>();
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < rawText().length(); i++) {
            char c = rawText().charAt(i);
            if (' ' == c && !isInQuote) {
                if (stringBuilder.length() != 0) {
                    result.add(stringBuilder.toString());
                    stringBuilder.delete(0, stringBuilder.length());
                }
            } else if ('\'' == c || '"' == c) {
                if (isInQuote) {
                    isInQuote = false;
                    result.add(stringBuilder.toString());
                    stringBuilder.delete(0, stringBuilder.length());
                } else {
                    isInQuote = true;
                }
            } else {
                stringBuilder.append(c);
            }
        }
        return result;
    }
}

上面的MockCommandLineInput方法重写了父接口Input中的words方法,它不会拆分单引号和双引号引起来的字符串。

最终使用shell.evaluate的方法如下所示:

Object result = shell.evaluate((MockCommandLineInput) () -> "someCommand --sql 'select * from table'");

执行外部命令

我们可以将需要spring shell执行的一连串命令存放到文件中,例如/path/to/command-file.txt,然后通过spring shell批量执行它们。

java -jar xxx.jar script --file /path/to/command-file.txt

参考文献

https://docs.spring.io/spring-shell/docs/current/reference/htmlsingle

https://docs.spring.io/spring-shell/docs/2.1.1/site/reference/htmlsingle

本博客为作者原创,欢迎大家参与讨论和批评指正。如需转载请注明出处。

相关文章

网友评论

    本文标题:Spring shell 简易使用指南

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