本文档的副本可能供您自己使用和分发至 其他,前提是您不对此类副本和进一步 前提是每个副本都包含此版权声明,无论是否在 打印或电子。
1. 什么是弹簧壳?
并非所有应用程序都需要花哨的 Web 用户界面。 有时,通过交互式终端与应用程序交互是 完成任务的最合适方法。
Spring Shell 允许您创建这样一个可运行的应用程序,其中 用户输入在程序终止之前运行的文本命令。 Spring Shell 项目提供了创建此类 REPL 的基础设施(Read, Eval, 打印循环)应用程序,让您专注于通过使用 熟悉的 Spring 编程模型。
Spring Shell 包括高级功能(例如解析、制表符补全、着色 输出,花哨的ASCII艺术表格显示,输入转换和验证),解放您 专注于核心命令逻辑。
Spring Shell 2.1.x 是一个重大的返工,使代码库与时俱进 现有的 Spring 引导版本,添加新功能,特别是 使其与GraalVM一起使用,这使得命令行应用程序变得很多 在 Java 空间中更相关。迁移到新的主要版本还可以让 我们清理代码库并进行一些必要的中断性更改。
2. 入门
为了了解Spring Shell提供了什么,我们可以编写一个简单的shell应用程序 有一个简单的命令来添加两个数字。
2.1. 编写一个简单的启动应用程序
从版本 2 开始,Spring Shell 已经从头开始重写了各种 考虑到增强功能,其中之一是与Spring Boot的轻松集成。
出于本教程的目的,我们通过以下方式创建一个简单的 Boot 应用程序 使用 start.spring.io。这个最小的应用程序仅依赖并配置生成可执行的 über-jar:spring-boot-starterspring-boot-maven-plugin
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency></dependencies>2.2. 添加对 Spring Shell 的依赖
开始使用Spring Shell的最简单方法是依赖工件。 它配备了使用Spring Shell所需的一切,并且可以很好地与Boot配合使用, 根据需要仅配置必要的 bean:{starter-artifactId}
<dependency> <groupId>org.springframework.shell</groupId> <artifactId>spring-shell-starter</artifactId> <version>2.1.4</version></dependency>鉴于 Spring Shell 启动 REPL(读取-评估-打印-循环)是因为存在这种依赖关系, 在本教程中,您需要在构建 () 时跳过测试,或者删除示例集成测试 这是由 start.spring.io 产生的。如果不删除它,集成测试将创建 Spring 和,根据您的构建工具,卡在评估循环中或与 NPE 一起崩溃。-DskipTestsApplicationContext
2.3. Your First Command
Now we can add our first command. To do so, create a new class (named whatever you want) and annotate it with (a variation of that is used to restrict the set of classes that are scanned for candidate commands).@ShellComponent@Component
Then we can create an method that takes two ints ( and ) and returns their sum. We need to annotate it with and provide a description of the command in the annotation (the only piece of information that is required):addab@ShellMethod
package com.example.demo;import org.springframework.shell.standard.ShellMethod;import org.springframework.shell.standard.ShellComponent;@ShellComponentpublic class MyCommands { @ShellMethod("Add two integers together.") public int add(int a, int b) { return a + b; }}2.4. 试用应用程序
要构建应用程序并运行生成的 jar,请运行以下命令:
./mvnw clean install -DskipTests[...]java -jar target/demo-0.0.1-SNAPSHOT.jarshell:>黄色提示符邀请您键入命令。键入、按下并欣赏魔法:shell:>add 1 2ENTER
shell:>add --a 1 --b 23你应该玩 shell(提示:有一个命令)。完成后,键入并按 。helpexitENTER
本文档的其余部分将深入探讨整个 Spring Shell 编程模型。
3. 基础知识
本节介绍 Spring Shell 的基础知识。在继续定义实际命令和选项之前, 我们需要了解Spring Shell的一些基本概念。
从本质上讲,在拥有正常工作的 Spring Shell 应用程序之前,需要做一些事情:
- 创建一个 Spring Boot 应用程序。
- 定义命令和选项。
- 打包应用程序。
- 以交互方式或非交互方式运行应用程序。
您可以获得一个完整的 Spring Shell 应用程序,而无需定义任何用户级命令 因为提供了一些基本的内置命令(例如 和 )。helphistory
在本文档中,我们引用了使用 注释(主要涉及 和 的使用)和 编程方式(使用 )。@ShellMethod@ShellOptionCommandRegistration
编程模型是实际注册事物的方式,即使您使用注释也是如此。 和注释是旧版功能 我们还不想删除。 是新的发展 添加新功能的模型。我们很可能会取代现有的 带有更好内容的注释,以支持模型中的新功能。@ShellMethod@ShellOptionCommandRegistrationCommandRegistration
4. 命令
在本节中,我们将介绍实际的命令注册并保留命令选项 并在稍后的文档中执行。可以在命令注册中找到更多详细信息。
有两种不同的方法来定义命令:通过注释模型和 通过程序化模型。在注释模型中,定义方法 在类中,并使用特定注释批注类和方法。在程序化模型中, 使用更低级的方法,定义命令注册( 作为 Bean 或通过动态注册到命令目录)。
4.1. 注释模型
当您使用标准 API 时,bean 上的方法将转换为可执行命令,前提是:
- Bean 类带有注释。(这用于限制豆子集 被考虑在内。@ShellComponent
- 该方法带有注释。@ShellMethod
这是一个刻板印象注释,它本身是用 进行元注释的。结果, 除了过滤机制之外,还可以使用它来声明 bean(例如,通过使用 )。@ShellComponent@Component@ComponentScan
您可以使用注释的属性自定义所创建 Bean 的名称。value
@ShellComponentstatic class MyCommands { @ShellMethod public void mycommand() { }}注释唯一必需的属性是其属性,它应该具有 对命令功能的简短、一句话的描述。这让您的用户 获取有关命令的一致帮助,而无需离开 shell(请参阅帮助)。@ShellMethodvalue
命令的描述应该简短——不超过一两句话。为了更好 一致性,它应该以大写字母开头,以句点结尾。
默认情况下,无需指定命令的键(即应使用的单词) 在命令行管理程序中调用它)。方法名称用作命令键,将驼峰大小写名称转换为 虚线、GNU 样式的名称(例如,变为 )。sayHello()say-hello
但是,您可以使用批注的属性显式设置命令键:key
@ShellMethod(value = "Add numbers.", key = "sum")public int add(int a, int b) { return a + b;}该属性接受多个值。 如果为单个方法设置多个键,则会使用这些不同的别名注册该命令。key
命令键几乎可以包含任何字符,包括空格。不过,在想出名字时, 请记住,一致性通常受到用户的赞赏。也就是说,您应该避免将虚线名称与 间隔名称和其他不一致之处。
4.2. 程序化模型
在编程模型中,定义为 ,并自动注册:CommandRegistration@Bean
@BeanCommandRegistration commandRegistration() { return CommandRegistration.builder() .command("mycommand") .build();}4.3. 组织命令
当你的 shell 开始提供很多功能时,你最终可能会 有很多命令,这可能会让您的用户感到困惑。通过键入 , 他们会看到一个令人生畏的命令列表,按字母顺序组织, 这可能并不总是显示可用命令的最佳方式。help
为了减轻这种可能的混淆,Spring Shell 提供了将命令组合在一起的功能, 具有合理的默认值。然后,相关命令将在同一组中结束(例如,) 并一起显示在帮助屏幕和其他地方。User Management Commands
默认情况下,命令根据实现它们的类进行分组, 将驼峰类名转换为单独的单词(因此变为 )。 这是一个明智的默认值,因为无论如何,相关命令通常已经在类中, 因为他们需要使用相同的协作对象。URLRelatedCommandsURL Related Commands
但是,如果此行为不适合您,则可以覆盖该组 按优先级顺序按以下方式命令:
下面的清单显示了一个示例:
public class UserCommands { @ShellMethod(value = "This command ends up in the 'User Commands' group") public void foo() {} @ShellMethod(value = "This command ends up in the 'Other Commands' group", group = "Other Commands") public void bar() {}}...@ShellCommandGroup("Other Commands")public class SomeCommands { @ShellMethod(value = "This one is in 'Other Commands'") public void wizz() {} @ShellMethod(value = "And this one is 'Yet Another Group'", group = "Yet Another Group") public void last() {}}4.4. 动态命令可用性
由于应用程序的内部状态,注册的命令并不总是有意义的。 例如,可能有一个命令,但它仅在用户在遥控器上使用后才有效 服务器。现在,如果用户尝试使用该命令,shell 应该解释 该命令存在,但当时不可用。 Spring Shell 可以让你做到这一点,甚至让你提供一个简短的解释,说明原因 命令不可用。downloadconnectdownload
命令有三种可能的方法指示可用性。 它们都使用返回 . 请考虑以下示例:Availability
@ShellComponentpublic class MyCommands { private boolean connected; @ShellMethod("Connect to the server.") public void connect(String user, String password) { [...] connected = true; } @ShellMethod("Download the nuclear codes.") public void download() { [...] } public Availability downloadAvailability() { return connected ? Availability.available() : Availability.unavailable("you are not connected"); }}该方法用于连接到服务器(详细信息省略),更改状态 完成后通过布尔值的命令。 由于存在,在用户连接之前标记为不可用的命令 与名称中带有后缀的命令方法完全相同的方法。 该方法返回 的实例,该实例使用两个工厂方法之一构造。 如果该命令不可用,则必须提供解释。 现在,如果用户尝试在未连接的情况下调用该命令,则会发生以下情况:connectconnecteddownloaddownloadAvailabilityAvailability
shell:>downloadCommand 'download' exists but is not currently available because you are not connected.Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.有关当前不可用命令的信息也用于集成帮助中。请参阅帮助。
如果将命令不可用时提供的原因附加到“因为”之后,则应很好地阅读。
您不应以大写字母开头句子或添加最后一个句点
如果在命令方法的名称后命名可用性方法不适合您,则 可以使用注释提供显式名称:@ShellMethodAvailability
@ShellMethod("Download the nuclear codes.") @ShellMethodAvailability("availabilityCheck") public void download() { [...] } public Availability availabilityCheck() { return connected ? Availability.available() : Availability.unavailable("you are not connected"); }名称必须匹配
最后,通常情况下,同一类中的多个命令共享相同的内部状态,因此, 应作为一个组全部可用或不可用。Spring Shell 不必在所有命令方法上粘贴,而是允许您翻转内容并将注释放在可用性方法上,指定它控制的命令的名称:@ShellMethodAvailability@ShellMethodAvailabilty
@ShellMethod("Download the nuclear codes.") public void download() { [...] } @ShellMethod("Disconnect from the server.") public void disconnect() { [...] } @ShellMethodAvailability({"download", "disconnect"}) public Availability availabilityCheck() { return connected ? Availability.available() : Availability.unavailable("you are not connected"); }该属性的缺省值为 。这个特别的 通配符匹配所有命令名称。这使得打开或关闭单个类的所有命令变得容易 使用单一可用性方法:@ShellMethodAvailability.value()*
@ShellComponentpublic class Toggles { @ShellMethodAvailability public Availability availabilityOnWeekdays() { return Calendar.getInstance().get(DAY_OF_WEEK) == SUNDAY ? Availability.available() : Availability.unavailable("today is not Sunday"); } @ShellMethod public void foo() {} @ShellMethod public void bar() {}}Spring Shell 对如何编写命令和如何组织类没有施加太多限制。 但是,将相关命令放在同一个类中通常是一种很好的做法,并且可用性指示器 可以从中受益。
4.5. 退出代码
许多命令行应用程序在适用时返回运行环境的退出代码 可用于区分命令是否已成功执行。在 这主要与命令在非交互模式下运行时有关,这意味着一个命令 始终使用 .spring-shellspring-shell
退出代码的默认行为为:
- 命令选项解析中的错误将导致代码2
- 任何一般错误将导致结果代码1
- 显然,在任何其他情况下,结果代码是0
每个都可以在异常和退出代码之间定义自己的映射。 从本质上讲,我们绑定到关于退出代码的功能,并且简单地 融入其中。CommandRegistrationSpring Boot
假设下面有一个异常显示,它将从命令中抛出:
static class MyException extends RuntimeException { private final int code; MyException(String msg, int code) { super(msg); this.code = code; } public int getCode() { return code; }}可以在和退出代码之间定义映射函数。你也可以 只需配置一个类即可退出代码,这只是配置中的语法糖。Throwable
CommandRegistration.builder() .withExitCode() .map(MyException.class, 3) .map(t -> { if (t instanceof MyException) { return ((MyException) t).getCode(); } return 0; }) .and() .build();退出代码不能使用基于注释的配置进行自定义
5. 选项
命令行参数可以分为选项和位置参数。 以下各节介绍如何定义和使用选项的功能。
5.1. 定义
选项可以在目标方法中定义为方法参数中的注释 或以编程方式使用 .CommandRegistration
具有带参数的目标方法会自动注册到匹配项 参数名称。
public String example(String arg1) { return "Hello " + arg1;}@ShellOption注释可用于定义选项名称,如果 不希望它与参数名称相同。
public String example(@ShellOption(value = { "--argx" }) String arg1) { return "Hello " + arg1;}如果定义选项名称时不带前缀,则发现 或 来自 ShellMethod#前缀。---
public String example(@ShellOption(value = { "argx" }) String arg1) { return "Hello " + arg1;}编程方式是使用方法添加一个长名称。CommandRegistration
CommandRegistration.builder() .withOption() .longNames("arg1") .and() .build();5.2. 短格式
大多数短样式 POSIX 选项只是长格式的同义词,但 添加附加功能以将这些选项组合在一起。有短 选项 A、B、C 可用作 。-abc
以编程方式使用短名称函数定义短选项。
CommandRegistration.builder() .withOption() .shortNames('a') .and() .withOption() .shortNames('b') .and() .withOption() .shortNames('c') .and() .build();Short option with combined format is powerful if type is defined as a flag which means type is a boolean. That way you can define a presense of a flags as , or .-abc-abc true-abc false
CommandRegistration.builder() .withOption() .shortNames('a') .type(boolean.class) .and() .withOption() .shortNames('b') .type(boolean.class) .and() .withOption() .shortNames('c') .type(boolean.class) .and() .build();使用注释模型,您可以直接定义短参数。
public String example( @ShellOption(value = { "-a" }) String arg1, @ShellOption(value = { "-b" }) String arg2, @ShellOption(value = { "-c" }) String arg3) { return "Hello " + arg1;}5.3. 阿里蒂
有时,您希望使用选项更精细地控制参数的数量 在发生分析操作时进行处理。Arity 定义为最小值和最大值 值,其中 min 必须是正整数,max 必须大于或等于 min。
CommandRegistration.builder() .withOption() .longNames("arg1") .arity(0, 1) .and() .build();Arity 也可以定义为枚举,它们是快捷方式 在下表中:OptionArity
CommandRegistration.builder() .withOption() .longNames("arg1") .arity(OptionArity.EXACTLY_ONE) .and() .build();Table 1. OptionArity
价值
最小/最大
零
0 / 0
ZERO_OR_ONE
0 / 1
EXACTLY_ONE
1 / 1
ZERO_OR_MORE
0 / 整数最大值
ONE_OR_MORE
1 / 整数最大值
注释模型仅支持定义 arity 的最大值。
public String example(@ShellOption(arity = 1) String arg1) { return "Hello " + arg1;}5.4. 位置
位置信息主要与命令目标方法相关:
CommandRegistration.builder() .withOption() .longNames("arg1") .position(0) .and() .build();5.5. Optional Value
An option is either required or not and, generally speaking, how it behaves depends on a command target:
CommandRegistration.builder() .withOption() .longNames("arg1") .required() .and() .build();In the annotation model, there is no direct way to define if argument is optional. Instead, it is instructed to be :NULL
public String example( @ShellOption(defaultValue = ShellOption.NULL) String arg1) { return "Hello " + arg1;}5.6. Default Value
Having a default value for an option is somewhat related to Optional Value, as there are cases where you may want to know if the user defined an option and change behavior based on a default value:
CommandRegistration.builder() .withOption() .longNames("arg1") .defaultValue("defaultValue") .and() .build();注释模型还支持定义缺省值:
public String example( @ShellOption(defaultValue = "defaultValue") String arg1) { return "Hello " + arg1;}5.7. 验证
Spring Shell 与 Bean Validation API 集成以支持 对命令参数的自动和自记录约束。
在命令参数上找到的注释和方法级别的注释是 在执行命令之前遵循并触发验证。请考虑以下命令:
@ShellMethod("Change password.") public String changePassword(@Size(min = 8, max = 40) String password) { return "Password successfully set to " + password; }从前面的示例中,您可以免费获得以下行为:
shell:>change-password helloThe following constraints were not met: --password string : size must be between 8 and 40 (You passed 'hello')5.8. 标签
选项标签在 shell 本身中没有功能行为,除了 默认命令输出的内容。在命令文档中 记录了一种类型的选项,但这并不总是非常有用。因此 您可能希望为选项提供更好的描述性词。help
CommandRegistration.builder() .withOption() .longNames("arg1") .and() .withOption() .longNames("arg2") .label("MYLABEL") .and() .build();定义标签随后显示在 中。help
my-shell:>help mycommandNAME mycommand -SYNOPSIS mycommand --arg1 String --arg2 MYLABELOPTIONS --arg1 String [Optional] --arg2 MYLABEL [Optional]6. 完成
Spring Shell 可以为两个交互式 shell 提供完成建议 和命令行。但是,当外壳在 交互模式我们有一个 shell 的活动实例,这意味着它是 更容易提供更多编程的方式来提供完成提示。 当 shell 纯粹作为命令行工具运行时,完成只能 通过集成到操作系统级别的shell中完成,例如bash。
6.1. 互动
完成提示是使用函数或界面样式计算的 获取并返回实例列表的方法。 给你各种 有关当前上下文(如命令注册和选项)的信息。CompletionContextCompletionProposalCompletionContext
通用解析器可以注册为 bean(如果它们有用) 对于所有命令和场景。例如现有完成 实现处理完成 作为选项名称。RegistrationOptionsCompletionResolver
static class MyValuesCompletionResolver implements CompletionResolver { @Override public List<CompletionProposal> apply(CompletionContext t) { return Arrays.asList("val1", "val2").stream() .map(CompletionProposal::new) .collect(Collectors.toList()); }}具有基于生成器的命令注册的选项值可以是 按选项定义。
void dump1() { CommandRegistration.builder() .withOption() .longNames("arg1") .completion(ctx -> { return Arrays.asList("val1", "val2").stream() .map(CompletionProposal::new) .collect(Collectors.toList()); }) .and() .build();}处理具有基于注释的命令注册的选项值 通过可以通过注释定义的接口。ValueProvider@ShellOption
static class MyValuesProvider implements ValueProvider { @Override public List<CompletionProposal> complete(CompletionContext completionContext) { return Arrays.asList("val1", "val2").stream() .map(CompletionProposal::new) .collect(Collectors.toList()); }}实际使用基于注释的命令需要 注册为豆子。ValueProvider
@ShellMethod(value = "complete", key = "complete")public String complete( @ShellOption(valueProvider = MyValuesProvider.class) String arg1){ return "You said " + arg1;}6.2. 命令行
命令行完成目前仅支持 bash 并已记录在案 在内置命令完成中。completion
7. 建筑
本节介绍如何构建 Spring Shell 应用程序。
7.1. 原生支持
版本 2.1.x 包括对编译 Spring Shell 应用程序的实验性支持 使用GraalVM和Spring Native进入本机应用程序。因为底层的JLine 库适用于 GraalVM,大多数事情都应该正常工作。
您可以使用本机配置文件编译项目以获取本机应用程序:
$ ./mvnw clean package -Pnative然后,您可以在交互或非交互模式下运行应用程序:
$ ./spring-shell-samples/target/spring-shell-samples helpAVAILABLE COMMANDSBuilt-In Commands completion bash: Generate bash completion script help: Display help about available commands. history: Display or save the history of previously run commands script: Read and execute commands from a file....8. 组件
组件是一组内置功能或其他功能 您可以根据自己的需要重复使用或扩展。有问题的组件是 内置命令或提供更高级别 UI 端组件 命令本身中的功能。
8.1. 内置命令
8.1.1. 帮助
运行 shell 应用程序通常意味着用户处于图形受限的状态 环境。此外,虽然在手机时代我们几乎总是连接, 访问 Web 浏览器或任何其他富 UI 应用程序(如 PDF 查看器)可能并不总是 是可能的。这就是为什么正确自我记录 shell 命令很重要的原因,这就是命令的用武之地。help
键入 + 列出 shell 已知的所有命令(包括不可用的命令) 以及对他们所做的事情的简短描述,类似于以下内容:helpENTER
my-shell:>helpAVAILABLE COMMANDSBuilt-In Commands exit: Exit the shell. help: Display help about available commands stacktrace: Display the full stacktrace of the last error. clear: Clear the shell screen. quit: Exit the shell. history: Display or save the history of previously run commands completion bash: Generate bash completion script version: Show version info script: Read and execute commands from a file.键入显示有关命令的更多详细信息,包括可用参数及其 类型,它们是否是强制性的,以及其他详细信息。help <command>
以下清单显示了应用于自身的命令:help
my-shell:>help helpNAME help - Display help about available commandsSYNOPSIS help --command StringOPTIONS --command or -C String The command to obtain help for. [Optional]帮助是模板化的,可以根据需要进行自定义。设置位于可用于禁用命令、执行或是否要通过平展隐藏组的位置 结构, 用于定义用于输出命令帮助的模板,用于定义 命令列表的输出。spring.shell.command.helpenabledgrouping-modegroupflatcommand-templatecommands-template
如果已设置,则帮助将显示:spring.shell.command.help.grouping-mode=flat
my-shell:>help helpAVAILABLE COMMANDSexit: Exit the shell.help: Display help about available commandsstacktrace: Display the full stacktrace of the last error.clear: Clear the shell screen.quit: Exit the shell.history: Display or save the history of previously run commandscompletion bash: Generate bash completion scriptversion: Show version infoscript: Read and execute commands from a file.输出自 和 都使用默认实现进行模板化 可以更改。helphelp <commmand>
选项默认为模型并作为模型传递。spring.shell.command.help.commands-templateclasspath:template/help-commands-default.stgGroupsInfoModel
选项默认为模型并作为模型传递。spring.shell.command.help.command-templateclasspath:template/help-command-default.stgCommandInfoModel
Table 2. GroupsInfoModel Variables
钥匙
描述
showGroups
true如果启用了“显示组”。否则,为假。
groups
命令变量(请参阅组命令信息模型变量)。
commands
命令变量(请参阅命令信息模型变量)。
hasUnavailableCommands
true如果有不可用的命令。否则,为假。
Table 3. GroupCommandInfoModel Variables
钥匙
描述
group
组的名称(如果已设置)。否则为空。
commands
命令(如果已设置)。否则为空。类型是一个多值,请参阅命令信息模型变量。
Table 4. CommandInfoModel Variables
钥匙
描述
name
命令的名称(如果已设置)。否则为空。类型为字符串,包含完整命令。
names
命令的名称(如果已设置)。否则为空。类型是多值本质上是拆分的。name
aliases
可能的别名(如果已设置)。类型是带有字符串的多值。
description
命令的说明(如果已设置)。否则为空。
parameters
参数变量(如果已设置)。否则为空。类型是一个多值,请参阅命令参数信息模型变量。
availability
可用性变量(请参阅命令可用性信息模型变量)。
Table 5. CommandParameterInfoModel Variables
钥匙
描述
type
参数的类型(如果已设置)。否则为空。
arguments
参数(如果已设置)。否则为空。类型是带有字符串的多值。
required
true如果需要。否则,为假。
description
参数的说明(如果已设置)。否则为空。
defaultValue
参数的默认值(如果已设置)。否则为空。
hasDefaultValue
true如果默认值存在。否则,为假。
Table 6. CommandAvailabilityInfoModel Variables
钥匙
描述
available
true如果可用。否则,为假。
reason
如果设置,则不可用的原因。否则为空。
8.1.2. 清除
该命令执行您的预期操作并清除屏幕,重置提示 在左上角。clear
8.1.3. 退出
该命令(也称为 )请求 shell 正常退出 关闭 Spring 应用程序上下文。如果不被覆盖,JLine Bean 会写入所有 命令,以便在下次启动时再次可用。quitexitHistory
8.1.4. 堆栈跟踪
当命令代码中发生异常时,shell 会捕获该异常,并显示一条简单的单行消息 以免用户溢出太多信息。 但是,在某些情况下,了解究竟发生了什么很重要(特别是如果异常具有嵌套原因)。
为此,Spring Shell 会记住上次发生的异常,用户稍后可以使用该命令在控制台上打印所有详细信息。stacktrace
8.1.5. 脚本
该命令接受本地文件作为参数,并重播在那里找到的命令,一次一个。script
从文件中读取的行为与交互式 shell 内部的行为完全相同,因此考虑以 开头的行 为注释并被忽略,而以触发行继续结尾的行。//\
8.1.6. 历史
该命令显示已执行命令的历史记录。history
有几个配置选项可用于配置行为 的历史。历史记录保存在日志文件中,默认情况下启用该文件,并且可以 通过设置 关闭。日志文件的名称 解析自 并默认为 , 您可以通过设置 .spring.shell.history.enabledspring.application.namespring-shell.logspring.shell.history.name
默认情况下,日志文件会生成到当前工作目录,您可以指示 通过设置 .此属性可以包含 一个占位符 (),它解析为公共共享配置目录。spring.shell.config.location{userconfig}
运行 Spring Shell 应用程序以查看示例应用程序在使用这些选项时的工作方式。
8.1.7. 完成
该命令集允许您创建可以使用的脚本文件 与 am OS shell 实现一起提供完成。这在以下情况下非常有用 使用非交互模式。completion
目前,唯一的实现是 bash,它与 sub-command 一起工作。bash
8.1.8. 版本
该命令通过集成到 引导的,如果这些存在于 shell 应用程序中。 默认情况下只显示版本信息,可以通过配置开启其他信息 选项。versionBuildPropertiesGitProperties
相关设置位于 下,您可以在其中使用 禁用命令,并可以选择使用 定义您自己的模板。您可以使用 、、、、、、 和 命令来控制 默认模板中的字段。spring.shell.command.versionenabledtemplateshow-build-artifactshow-build-groupshow-build-nameshow-build-timeshow-build-versionshow-git-branchshow-git-commit-idshow-git-short-commit-idshow-git-commit-time
模板默认为 ,您可以定义 您自己的,如以下示例所示:classpath:template/version-default.st
<buildVersion>此设置将输出如下所示的内容:
X.X.X可以将以下属性添加到默认模板呈现中:、、 和 。buildVersionbuildGroupbuildGroupbuildNamebuildTimegitShortCommitIdgitCommitIdgitBranchgitCommitTime
8.2. 流程
当您使用流组件构建涉及的内容时 使用多个组件,您的实现可能会变得有点混乱。 为了简化这些用例,我们添加了一个可以将多个组件执行挂接在一起的 作为“流”。ComponentFlow
以下清单显示了 shell 中的流及其输出的示例:
static class FlowSampleComplex { @Autowired private ComponentFlow.Builder componentFlowBuilder; public void runFlow() { Map<String, String> single1SelectItems = new HashMap<>(); single1SelectItems.put("key1", "value1"); single1SelectItems.put("key2", "value2"); List<SelectItem> multi1SelectItems = Arrays.asList(SelectItem.of("key1", "value1"), SelectItem.of("key2", "value2"), SelectItem.of("key3", "value3")); ComponentFlow flow = componentFlowBuilder.clone().reset() .withStringInput("field1") .name("Field1") .defaultValue("defaultField1Value") .and() .withStringInput("field2") .name("Field2") .and() .withConfirmationInput("confirmation1") .name("Confirmation1") .and() .withPathInput("path1") .name("Path1") .and() .withSingleItemSelector("single1") .name("Single1") .selectItems(single1SelectItems) .and() .withMultiItemSelector("multi1") .name("Multi1") .selectItems(multi1SelectItems) .and() .build(); flow.run(); }}组件的正常执行顺序与使用生成器定义的顺序相同。它 可以使用函数并返回目标组件 ID 有条件地选择在流中跳转的位置。如果此返回的 id 为 aither null 或不存在,则流基本上会在那里停止。next
static class FlowSampleConditional { @Autowired private ComponentFlow.Builder componentFlowBuilder; public void runFlow() { Map<String, String> single1SelectItems = new HashMap<>(); single1SelectItems.put("Field1", "field1"); single1SelectItems.put("Field2", "field2"); ComponentFlow flow = componentFlowBuilder.clone().reset() .withSingleItemSelector("single1") .name("Single1") .selectItems(single1SelectItems) .next(ctx -> ctx.getResultItem().get().getItem()) .and() .withStringInput("field1") .name("Field1") .defaultValue("defaultField1Value") .next(ctx -> null) .and() .withStringInput("field2") .name("Field2") .defaultValue("defaultField2Value") .next(ctx -> null) .and() .build(); flow.run(); }}运行流的结果返回 ,您可以 用于执行进一步的操作。ComponentFlowResult
8.3. 流量组件
从版本 2.1.x 开始,新的组件模型提供了 为通常用例创建更高级别的用户交互的更简单方法, 例如要求以各种形式输入。这些通常只是纯文本 输入或从列表中选择内容。
内置组件的模板位于类路径中。org/springframework/shell/component
内置组件通常遵循以下逻辑:
流为定义 更适合定义交互式命令流的组件。
8.3.1. 组件渲染
您可以通过以下两种方式之一实现组件渲染: 以编程方式或使用 ANTLR 字符串模板。 严格来说,有一个简单的渲染器接口 作为输入并输出 . 这使您可以在模板和代码之间进行选择。FunctionContextAttributedString
模板是一个不错的选择,如果你不需要做任何复杂的事情或 您只想稍微修改现有组件布局。渲染 然后,通过代码,您可以灵活地执行所需的任何操作。
以编程方式呈现是创建一个:Function
class StringInputCustomRenderer implements Function<StringInputContext, List<AttributedString>> { @Override public List<AttributedString> apply(StringInputContext context) { AttributedStringBuilder builder = new AttributedStringBuilder(); builder.append(context.getName()); builder.append(" "); if (context.getResultValue() != null) { builder.append(context.getResultValue()); } else { String input = context.getInput(); if (StringUtils.hasText(input)) { builder.append(input); } else { builder.append("[Default " + context.getDefaultValue() + "]"); } } return Arrays.asList(builder.toAttributedString()); }}然后,您可以将其挂接到组件:
@ShellMethod(key = "component stringcustom", value = "String input", group = "Components")public String stringInputCustom(boolean mask) { StringInput component = new StringInput(getTerminal(), "Enter value", "myvalue", new StringInputCustomRenderer()); component.setResourceLoader(getResourceLoader()); component.setTemplateExecutor(getTemplateExecutor()); if (mask) { component.setMaskCharater('*'); } StringInputContext context = component.run(StringInputContext.empty()); return "Got value " + context.getResultValue();}组件有自己的上下文,但通常共享一些功能 从父组件类型。下表显示了这些上下文变量:
Table 7. TextComponentContext Template Variables
钥匙
描述
resultValue
组件呈现其结果后的值。
name
组件的名称,即其标题。
message
组件的可能消息集。
messageLevel
消息的级别 — 、 或 之一。INFOWARNERROR
hasMessageLevelInfo
如果级别为 。否则,为假。trueINFO
hasMessageLevelWarn
如果级别为 。否则,为假。trueWARN
hasMessageLevelError
如果级别为 。否则,为假。trueERROR
input
原始用户输入。
Table 8. SelectorComponentContext Template Variables
钥匙
描述
name
组件的名称,即其标题。
input
原始用户输入 — 主要用于筛选。
itemStates
项目状态的完整列表。
itemStateView
项目状态的可见列表。
isResult
如果上下文处于结果模式,则返回。true
cursorRow
选择器中的当前游标行。
8.3.2. 字符串输入
字符串输入组件要求用户输入简单的文本,可以选择屏蔽值 如果内容包含敏感内容。下面的清单显示了一个示例:
@ShellComponentpublic class ComponentCommands extends AbstractShellComponent { @ShellMethod(key = "component string", value = "String input", group = "Components") public String stringInput(boolean mask) { StringInput component = new StringInput(getTerminal(), "Enter value", "myvalue"); component.setResourceLoader(getResourceLoader()); component.setTemplateExecutor(getTemplateExecutor()); if (mask) { component.setMaskCharater('*'); } StringInputContext context = component.run(StringInputContext.empty()); return "Got value " + context.getResultValue(); }}下图显示了字符串输入组件的典型输出:
上下文对象为 。下表列出了其上下文变量:StringInputContext
Table 9. StringInputContext Template Variables
钥匙
描述
defaultValue
默认值(如果已设置)。否则为空。
maskedInput
屏蔽的输入值
maskedResultValue
屏蔽结果值
maskCharacter
掩码字符(如果已设置)。否则为空。
hasMaskCharacter
true如果设置了掩码字符。否则,为假。
model
父上下文变量(请参阅文本组件上下文模板变量)。
8.3.3. 路径输入
路径输入组件要求用户提供 a 并提供有关路径本身的其他信息。Path
@ShellComponentpublic class ComponentCommands extends AbstractShellComponent { @ShellMethod(key = "component path", value = "Path input", group = "Components") public String pathInput() { PathInput component = new PathInput(getTerminal(), "Enter value"); component.setResourceLoader(getResourceLoader()); component.setTemplateExecutor(getTemplateExecutor()); PathInputContext context = component.run(PathInputContext.empty()); return "Got value " + context.getResultValue(); }}下图显示了路径输入组件的典型输出:
上下文对象为 。下表描述了其上下文变量:PathInputContext
Table 10. PathInputContext Template Variables
钥匙
描述
model
父上下文变量(请参阅文本组件上下文模板变量)。
8.3.4. 确认
确认组件要求用户进行简单的确认。它本质上是一个 是或否问题。
@ShellComponentpublic class ComponentCommands extends AbstractShellComponent { @ShellMethod(key = "component confirmation", value = "Confirmation input", group = "Components") public String confirmationInput(boolean no) { ConfirmationInput component = new ConfirmationInput(getTerminal(), "Enter value", !no); component.setResourceLoader(getResourceLoader()); component.setTemplateExecutor(getTemplateExecutor()); ConfirmationInputContext context = component.run(ConfirmationInputContext.empty()); return "Got value " + context.getResultValue(); }}下图显示了确认组件的典型输出:
上下文对象为 。下表描述了其上下文变量:ConfirmationInputContext
Table 11. ConfirmationInputContext Template Variables
钥匙
描述
defaultValue
默认值 — 或 。truefalse
model
父上下文变量(请参阅文本组件上下文模板变量)。
8.3.5. 单选
单个选择组件要求用户从列表中选择一个项目。它类似于一个简单的 保管箱实施。下面的清单显示了一个示例:
@ShellComponentpublic class ComponentCommands extends AbstractShellComponent { @ShellMethod(key = "component single", value = "Single selector", group = "Components") public String singleSelector() { SelectorItem<String> i1 = SelectorItem.of("key1", "value1"); SelectorItem<String> i2 = SelectorItem.of("key2", "value2"); List<SelectorItem<String>> items = Arrays.asList(i1, i2); SingleItemSelector<String, SelectorItem<String>> component = new SingleItemSelector<>(getTerminal(), items, "testSimple", null); component.setResourceLoader(getResourceLoader()); component.setTemplateExecutor(getTemplateExecutor()); SingleItemSelectorContext<String, SelectorItem<String>> context = component .run(SingleItemSelectorContext.empty()); String result = context.getResultItem().flatMap(si -> Optional.ofNullable(si.getItem())).get(); return "Got value " + result; }}下图显示了单个选择组件的典型输出:
上下文对象为 。下表描述了其上下文变量:SingleItemSelectorContext
Table 12. SingleItemSelectorContext Template Variables
钥匙
描述
value
组件存在时的返回值。
rows
可见项目,其中行包含名称和选定项目的映射。
model
父上下文变量(请参阅选择器组件上下文模板变量)。
您可以通过定义要公开的项目来预先选择该项目。这是 如果您知道默认值并允许用户仅按下即可做出选择,则很有用。 以下清单设置默认值:Enter
SelectorItem<String> i1 = SelectorItem.of("key1", "value1");SelectorItem<String> i2 = SelectorItem.of("key2", "value2");List<SelectorItem<String>> items = Arrays.asList(i1, i2);SingleItemSelector<String, SelectorItem<String>> component = new SingleItemSelector<>(getTerminal(), items, "testSimple", null);component.setDefaultExpose(i2);8.3.6. 多选
多选组件要求用户从列表中选择多个项目。 下面的清单显示了一个示例:
@ShellComponentpublic class ComponentCommands extends AbstractShellComponent { @ShellMethod(key = "component multi", value = "Multi selector", group = "Components") public String multiSelector() { List<SelectorItem<String>> items = new ArrayList<>(); items.add(SelectorItem.of("key1", "value1")); items.add(SelectorItem.of("key2", "value2", false, true)); items.add(SelectorItem.of("key3", "value3")); MultiItemSelector<String, SelectorItem<String>> component = new MultiItemSelector<>(getTerminal(), items, "testSimple", null); component.setResourceLoader(getResourceLoader()); component.setTemplateExecutor(getTemplateExecutor()); MultiItemSelectorContext<String, SelectorItem<String>> context = component .run(MultiItemSelectorContext.empty()); String result = context.getResultItems().stream() .map(si -> si.getItem()) .collect(Collectors.joining(",")); return "Got value " + result; }}下图显示了一个典型的多选组件:
上下文对象为 。下表描述了其上下文变量:MultiItemSelectorContext
Table 13. MultiItemSelectorContext Template Variables
钥匙
描述
values
组件存在时返回的值。
rows
可见项目,其中行包含名称、选定、行内和启用项目的映射。
model
父上下文变量(请参阅选择器组件上下文模板变量)。
9. 定制
本节介绍如何自定义外壳。
9.1. 主题
当前的终端实现功能丰富,通常可以显示 其他只是纯文本的东西。例如,文本的样式可以设置为粗体或具有不同的颜色。终端能够 显示 Unicode 表中的各种字符,例如表情符号,通常是 用于使外壳输出更漂亮。
Spring Shell 通过它的主题框架支持这些,该框架包含两个部分, 首先样式可用于更改文本类型,其次是图形如何 显示一些字符。然后将这两者组合在一起作为一个主题。
有关主题化内部的更多信息,请参阅主题化。
默认主题已命名,但可以使用属性进行更改。其他名为用途的内置主题 没有颜色样式,尽量不使用任何特殊图形。defaultspring.shell.theme.namedump
通过覆盖设置来修改现有样式。
static class MyStyleSettings extends StyleSettings { @Override public String highlight() { return super.highlight(); }}通过覆盖设置来修改现有图形。
static class MyFigureSettings extends FigureSettings { @Override public String error() { return super.error(); }}要创建新主题,请创建并提供您自己的样式和图形实现。ThemeSettings
static class MyThemeSettings extends ThemeSettings { @Override public StyleSettings styles() { return new MyStyleSettings(); } @Override public FigureSettings figures() { return new MyFigureSettings(); }}注册一个新 Bean,您可以在其中返回自定义和主题名称。ThemeThemeSettings
@Configurationstatic class CustomThemeConfig { @Bean Theme myTheme() { return new Theme() { @Override public String getName() { return "mytheme"; } @Override public ThemeSettings getSettings() { return new MyThemeSettings(); } }; }}如果要创建,可以使用 来解析样式 JLine 样式的字符串以编程方式和数字(如果需要) 主题人物更漂亮。ThemeResolver
@Autowiredprivate ThemeResolver resolver;void resolve() { String resolvedStyle = resolver.resolveStyleTag(StyleSettings.TAG_TITLE); // bold,fg:bright-white AttributedStyle style = resolver.resolveStyle(resolvedStyle); // jline attributed style from expression above String resolvedFigure = resolver.resolveFigureTag(FigureSettings.TAG_ERROR); // character i.e. U+2716 Heavy Multiplication X Emoji, cross}