diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index ce2b8217ec161dec7ac7cc90c66b7f9bfc2845cf..9b40e5f24e1e2abfa6b45dfb9c0936dc0f3ee1dd 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -616,6 +616,17 @@ module.exports = { initialOpenGroupIndex: 0 // 可选的, 默认值是 0 } ], + '/spring-shell/': [ + { + title: 'Spring Shell 文档', + sidebarDepth: 2, + collapsable: false, + children: [ + "/spring-shell/spring-shell.md", + ], + initialOpenGroupIndex: 0 // 可选的, 默认值是 0 + } + ], '/spring-statemachine/': [ { title: 'Spring Statemachine 文档', diff --git a/docs/spring-shell/README.md b/docs/spring-shell/README.md new file mode 100644 index 0000000000000000000000000000000000000000..120251b4cf2c61f17a8dad423aa2ed01a24fffed --- /dev/null +++ b/docs/spring-shell/README.md @@ -0,0 +1 @@ +# Spring Shell \ No newline at end of file diff --git a/docs/spring-shell/spring-shell.md b/docs/spring-shell/spring-shell.md new file mode 100644 index 0000000000000000000000000000000000000000..3ddf64f815c1c607a5d37153c5565a78b6f608cc --- /dev/null +++ b/docs/spring-shell/spring-shell.md @@ -0,0 +1,754 @@ +# Spring shell 参考文档 + +## Spring shell 是什么? + +并不是所有的应用程序都需要一个花哨的 Web 用户界面!有时,使用交互式终端与应用程序进行交互是完成工作的最合适方法。 + +Spring Shell 允许人们轻松地创建这样一个可运行的应用程序,其中用户将输入文本命令,这些命令将被执行,直到程序终止。 Spring Shell 项目提供了创建这样一个 REPL(读取、求值、打印循环)的基础设施,允许开发人员使用熟悉的 Spring 编程模型,专注于命令的实现。 + +高级功能,如解析、制表符完成、输出的彩色化、漂亮的 ASCII-Art 表格显示、输入转换和验证都是免费的,开发者只需专注于核心命令逻辑即可。 + +## 使用 Spring shell + +### 开始 + +为了了解 Spring Shell 所提供的功能,让我们编写一个简单的 Shell 应用程序,它有一个简单的命令,可以将两个数字相加。 + +#### 让我们编写一个简单的启动应用程序 + +从版本 2 开始, Spring Shell 已经从头开始重写,并考虑到了各种增强功能,其中之一是轻松地与 Spring Boot 集成,尽管这不是一个很强的需求。为了本教程的目的,让我们创建一个简单的引导应用程序,例如使用[start.spring.io](https://start.spring.io)。这个最小的应用程序仅依赖于`spring-boot-starter`并配置`spring-boot-maven-plugin`,生成一个可执行的 über-jar: + +``` +... + + + org.springframework.boot + spring-boot-starter + + ... +``` + +#### 在 Spring shell 上添加依赖项 + +使用 Spring shell 的最简单方法是依赖于`spring-shell-starter`工件。这是使用 shell 所需的所有功能,并在引导时很好地发挥作用,只根据需要配置必要的 bean: + +``` +... + + org.springframework.shell + spring-shell-starter + 2.0.1.RELEASE + +... +``` + +| |考虑到 Spring shell 将通过存在此依赖关系来启动并启动 REPL,
你将需要在整个教程中构建跳过测试(`-DskipTests`),或者删除由[start.spring.io](https://start.spring.io)生成的示例集成测试
。如果不这样做,集成测试将创建
Spring `ApplicationContext`,并且根据你的构建工具,它将停留在 eval 循环中,或者与 NPE 一起崩溃。| +|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +#### 你的第一个命令 + +是时候添加我们的第一个命令了。创建一个新的类(随意命名),并用`@ShellComponent`(`@Component`的变体,用于限制扫描候选命令的类集)对其进行注释。 + +然后,创建一个`add`方法,该方法接受两个 INTS(`a`和`b`)并返回它们的和。用`@ShellMethod`对其进行注释,并在注释中提供对该命令的描述(这是唯一需要的信息): + +``` +package com.example.demo; + +import org.springframework.shell.standard.ShellMethod; +import org.springframework.shell.standard.ShellComponent; + +@ShellComponent +public class MyCommands { + + @ShellMethod("Add two integers together.") + public int add(int a, int b) { + return a + b; + } +} +``` + +#### 让我们载它一程吧! + +构建应用程序并运行生成的 JAR,就像这样; + +``` +./mvnw clean install -DskipTests +[...] + +java -jar target/demo-0.0.1-SNAPSHOT.jar +``` + +下面的屏幕会欢迎你(横幅来自 Spring boot,可以自定义[as usual](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-banner)): + +``` + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v1.5.6.RELEASE) + +shell:> +``` + +下面是一个黄色的`shell:>`提示符,它邀请你键入命令。输入`add 1 2`然后进入并欣赏魔术! + +``` +shell:>add 1 2 +3 +``` + +尝试玩 shell(提示:有一个`help`命令),完成后,输入`exit`Enter。 + +本文的其余部分深入研究了整个 Spring shell 编程模型。 + +### 编写自己的命令 + +Spring shell 决定将方法转换为实际的 shell 命令的方式是完全可插入的(参见[Extending Spring Shell](#extending-spring-shell)),但是在 Spring shell2.x 中,推荐的编写命令的方式是使用本节中描述的新 API(即所谓的*标准*API)。 + +使用*标准*API,bean 上的方法将被转换为可执行命令,前提是 + +* Bean 类带有`@ShellComponent`注释。这用于限制被考虑的 bean 集。 + +* 该方法带有`@ShellMethod`注释。 + +| |`@ShellComponent`是一种原型注释本身,用`@Component`进行元注释。这样,除了可以使用
的过滤机制外,还可以使用*声明*bean(* 例如 * 使用`@ComponentScan`)。

所创建的 Bean 的名称可以使用注释的`value`属性进行自定义。| +|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +#### 这一切都是关于文档的! + +`@ShellMethod`注释唯一需要的属性是其`value`属性,它应该用来写一个简短的一句话,描述命令的功能。这一点很重要,这样用户就可以在不必离开 shell 的情况下获得关于命令的一致帮助(参见[集成文档`help`命令](#help-command))。 + +| |对你的命令的描述应该简短,只需一两句话。为了更好的一致性,
建议它以大写字母开头,以点结尾。| +|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +#### 自定义命令名 + +默认情况下,不需要为你的命令指定*钥匙*(即 * 应该在 shell 中调用它的单词)。方法的名称将被用作命令键,将 camelcase 名称转换为 dashed、gnu 样式的名称(即`sayHello()`将变为`say-hello`)。 + +但是,可以使用注释的`key`属性显式设置命令键,如下所示: + +``` + @ShellMethod(value = "Add numbers.", key = "sum") + public int add(int a, int b) { + return a + b; + } +``` + +| |`key`属性接受多个值。
如果你为一个方法设置多个键,那么将使用这些不同的别名注册该命令。| +|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| + +| |命令键可以包含几乎任何字符,包括空格。但是,在使用名称时,
要记住,一致性通常是用户喜欢的(例如,* 避免将虚线名称与间隔名称混合,*等等。*)| +|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +### 调用你的命令 + +#### 按名称*vs.*位置参数 + +如上所述,用`@ShellMethod`装饰方法是创建命令的唯一要求。这样做时,用户可以通过两种可能的方式设置所有方法参数的值: + +* 使用参数键(* 例如 *`--arg value`)。这种方法被称为“按名称”参数。 + +* 或者在没有键的情况下,只需按照方法签名中出现的相同顺序设置参数值(“位置”参数)。 + +这两种方法可以混合和匹配,命名参数总是优先考虑(因为它们不太容易出现歧义)。因此,给出以下命令 + +``` + @ShellMethod("Display stuff.") + public String echo(int a, int b, int c) { + return String.format("You said a=%d, b=%d, c=%d", a, b, c); + } +``` + +然后,下面的调用都是等价的,如输出所示: + +``` +shell:>echo 1 2 3 (1) +You said a=1, b=2, c=3 + +shell:>echo --a 1 --b 2 --c 3 (2) +You said a=1, b=2, c=3 + +shell:>echo --b 2 --c 3 --a 1 (3) +You said a=1, b=2, c=3 + +shell:>echo --a 1 2 3 (4) +You said a=1, b=2, c=3 + +shell:>echo 1 --c 3 2 (5) +You said a=1, b=2, c=3 +``` + +|**1**|这使用了位置参数| +|-----|----------------------------------------------------------------| +|**2**|这是一个完整的副名称参数的示例| +|**3**|可以根据需要对副名称参数进行重新排序。| +|**4**|你可以混合使用这两种方法。| +|**5**|非副名称参数按其出现的顺序进行解析。| + +##### 自定义命名参数键 + +如上所示,派生命名参数的键的默认策略是使用方法签名的 Java 名称,并在其前加上两个破折号(`--`)。这可以通过两种方式进行定制: + +1. 要更改整个方法的默认前缀,请使用`prefix()`注释的`@ShellMethod`属性 + +2. 要以每参数方式重写*整体*键,请使用`@ShellOption`注释对参数进行注释。 + +看看下面的例子: + +``` + @ShellMethod(value = "Display stuff.", prefix="-") + public String echo(int a, int b, @ShellOption("--third") int c) { + return String.format("You said a=%d, b=%d, c=%d", a, b, c); + } +``` + +对于这样的设置,可能的参数键是`-a`、`-b`和`--third`。 + +| |可以为单个参数指定多个键。如果是这样,这些将以相互排斥的方式
指定相同的参数(因此只能使用其中的一个)。例如,下面是内置[
](#help-command)命令的签名:

```
@ShellMethod("Describe a command.")
public String help(@ShellOption({"-C", "--command"}) String command) {
...
}
```| +|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +#### 可选参数和默认值 + +Spring Shell 提供了给出参数默认值的能力,这将允许用户省略那些参数: + +``` + @ShellMethod("Say hello.") + public String greet(@ShellOption(defaultValue="World") String who) { + return "Hello " + who; + } +``` + +现在,`greet`命令仍然可以作为`greet Mother`(或`greet --who Mother`)调用,但也可以执行以下操作: + +``` +shell:>greet +Hello World +``` + +#### 参数有序度 + +到目前为止,一直假定每个参数映射到用户输入的单个单词。但是,当参数值应该是*多值*时,可能会出现这种情况。这是由`@ShellOption`注释的`arity()`属性驱动的。只需为参数类型使用集合或数组,并指定需要多少个值: + +``` + @ShellMethod("Add Numbers.") + public float add(@ShellOption(arity=3) float[] numbers) { + return numbers[0] + numbers[1] + numbers[2]; + } +``` + +然后可以使用以下任何语法调用该命令: + +``` +shell:>add 1 2 3.3 +6.3 +shell:>add --numbers 1 2 3.3 +6.3 +``` + +| |当使用*副名*参数方法时,应该重复使用**不是**键。以下是**不是**的工作:

```
shell:>add --numbers 1 --numbers 2 --numbers 3.3
```| +|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +##### 无限性 + +TO BE IMPLEMENTED + +##### 布尔参数的特殊处理 + +当涉及到参数的有用性时,有一种参数在默认情况下会受到特殊的处理,这在命令行实用程序中通常是这样的。布尔参数(即`boolean`以及`java.lang.Boolean`)的行为就像默认情况下它们的`arity()`的`0`一样,允许用户使用“标志”方法设置它们的值。请看以下内容: + +``` + @ShellMethod("Terminate the system.") + public String shutdown(boolean force) { + return "You said " + force; + } +``` + +这允许以下调用: + +``` +shell:>shutdown +You said false +shell:>shutdown --force +You said true +``` + +| |这种特殊处理与[默认值](#optional-parameters-default-values)规范配合得很好。虽然布尔参数的默认
是将其默认值设置为`false`,但是你可以另外指定(* 即 *`@ShellOption(defaultValue="true")`),并且行为将被反转(也就是说,不指定参数
将导致值为`true`,并且指定该标志将导致值`false`)| +|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +| |具有这种隐式`arity()=0`的行为可以防止用户指定一个值(* 例如 *`shutdown --force true`)。
如果你希望允许这种行为(并且放弃该标志方法),则使用注释强制执行`1`的项:

```
@ShellMethod("Terminate the system.")
public String shutdown(@ShellOption(arity=1, defaultValue="false") boolean force) {
return "You said " + force;
}
```| +|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +#### 报价处理 + +Spring Shell 接受用户输入并在*文字*中对其进行标记,在空格字符上进行分割。如果用户希望提供一个包含空格的参数值,则需要引用该值。支持单引号(`'`)和双引号(`"`),这些引号不会成为值的一部分: + +``` + @ShellMethod("Prints what has been entered.") + public String echo(String what) { + return "You said " + what; + } +``` + +``` +shell:>echo Hello +You said Hello +shell:>echo 'Hello' +You said Hello +shell:>echo 'Hello World' +You said Hello World +shell:>echo "Hello World" +You said Hello World +``` + +支持单引号和双引号,允许用户轻松地将一种类型的引号嵌入到一个值中: + +``` +shell:>echo "I'm here!" +You said I'm here! +shell:>echo 'He said "Hi!"' +You said He said "Hi!" +``` + +如果用户需要嵌入用于引用整个参数的相同类型的引用,则转义序列使用反斜杠(`\`)字符: + +``` +shell:>echo 'I\'m here!' +You said I'm here! +shell:>echo "He said \"Hi!\"" +You said He said "Hi!" +shell:>echo I\'m here! +You said I'm here! +``` + +在不使用附加引号的情况下,也可以转义空格字符,例如: + +``` +shell:>echo This\ is\ a\ single\ value +You said This is a single value +``` + +#### 与壳相互作用 + +Spring shell 项目构建在[JLine](https://github.com/jline/jline3)库的基础上,因此带来了许多不错的交互特性,其中一些特性将在本节中详细介绍。 + +首先也是最重要的是, Spring shell 几乎在所有可能的地方都支持选项卡补全。因此,如果有一个`echo`命令,并且用户按 E,C,Tab,那么`echo`就会出现。如果有几个以`ec`开头的命令,那么将提示用户进行选择(使用 tab orshift+tab 进行导航,并输入以进行选择)。 + +但完成并不止于命令键。如果应用程序开发人员注册了适当的 bean(参见[提供 TAB 完成建议](#providing-tab-completion)),它也适用于参数键(`--arg`)甚至参数值。 + +Spring Shell 应用程序的另一个不错的功能是支持行延续。如果一个命令及其参数太长,且不能很好地在屏幕上显示,那么用户可以对其进行分块,并用反斜杠(`\`)结束一行,然后在下一行按 Enter 并继续。在提交了整个命令后,这将被解析为用户在换行时输入了单个空格。 + +``` +shell:>register module --type source --name foo \ (1) +> --uri file:///tmp/bar +Successfully registered module 'source:foo' +``` + +|**1**|命令在下一行继续| +|-----|------------------------------| + +如果用户打开了一个引号(参见[报价处理](#quotes-handling))并在仍处于引号中时单击 Enter,则行延续也会自动触发: + +``` +shell:>echo "Hello (1) +dquote> World" +You said Hello World +``` + +|**1**|用户按下回车键| +|-----|-----------------------| + +最后, Spring Shell 应用程序受益于许多键盘快捷方式,你在使用常规的 OS Shell 时可能已经熟悉这些快捷方式,这些快捷方式是从 Emacs 借来的。值得注意的快捷方式包括 Ctrl+R 执行反向搜索,Ctrl+A 和 Ctrl+E RESP 地移动到行的开始和结束,或 Esc f 和 Desc B 一次移动一个单词。 + +##### 提供 TAB 完成建议 + +TBD + +### 验证命令参数 + +Spring Shell 与[Bean Validation API](http://beanvalidation.org/)集成,以支持对命令参数的自动和自记录约束。 + +在命令参数上发现的注释以及在方法级别上的注释将得到尊重,并在执行命令之前触发验证。给出以下命令: + +``` + @ShellMethod("Change password.") + public String changePassword(@Size(min = 8, max = 40) String password) { + return "Password successfully set to " + password; + } +``` + +你将免费获得这种行为: + +``` +shell:>change-password hello +The following constraints were not met: + --password string : size must be between 8 and 40 (You passed 'hello') +``` + +| |应用于所有的命令实现

重要的是要注意, Bean 验证适用于所有的命令实现,不管
它们是通过使用适配器使用“标准”API 还是任何其他 API(参见[支持其他 API](#support-for-shell-1-and-jcommander))| +|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +### 动态命令可用性 + +有时,由于应用程序的内部状态,注册的命令可能没有意义。例如,可能有一个`download`命令,但它仅在用户在远程服务器上使用`connect`时才起作用。现在,如果用户尝试使用`download`命令,那么 shell 应该很好地解释*是吗?*命令的存在,但是它当时是不可用的。 Spring Shell 允许开发人员这样做,甚至提供对命令不可用的原因的简短解释。 + +命令有三种可能的方式来指示可用性。它们都利用了返回`Availability`实例的 no-arg 方法。让我们从一个简单的例子开始: + +``` +@ShellComponent +public 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"); + } +} +``` + +在这里你可以看到`connect`方法用于连接到服务器(详细信息从中省略),在完成时通过`connected`布尔命令来改变命令的状态。在用户连接之前,`download`命令将被标记为*不可用*,这要感谢存在一个名为`download`命令方法的方法,该方法的名称后缀为`Availability`。该方法返回`Availability`的实例,该实例是用两个工厂方法中的一个方法构造的。如果命令不可用,则必须提供解释。现在,如果用户试图在未连接的情况下调用该命令,将会发生以下情况: + +``` +shell:>download +Command '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. +``` + +集成帮助还利用了有关当前不可用命令的信息。请参阅[使用`help`命令的集成文档]。 + +| |如果在“because…”

后面附加了“because”,那么当命令不可用时提供的原因应该很好地理解为“because”
,最好不要以大写字母开头,也不要加上最后一个点。| +|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +如果由于某种原因,在命令方法的名称之后命名可用性方法不适合你,那么可以使用`@ShellMethodAvailability`提供一个显式名称,如下所示: + +``` + @ShellMethod("Download the nuclear codes.") + @ShellMethodAvailability("availabilityCheck") (1) + public void download() { + [...] + } + + public Availability availabilityCheck() { (1) + return connected + ? Availability.available() + : Availability.unavailable("you are not connected"); + } +``` + +|**1**|名字必须匹配。| +|-----|-----------------------| + +最后,通常的情况是,同一个类中的几个命令共享相同的内部状态,因此它们应该都是可用的或不可用的。 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()`属性的默认值是`"*"`,这是一个特殊的
通配符,它匹配所有命令名。因此,只需一个可用性方法,就可以轻松地打开或关闭单个类的
的所有命令。下面是一个例子:

```
@ShellComponent
public 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 并没有施加太多的约束。
但是将相关的命令放在同一个类中通常是很好的实践,可用性指标
可以从中受益。| +|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +### 组织命令 + +当你的 shell 开始提供大量功能时,你可能会使用大量命令,这可能会使你的用户感到困惑。输入`help`,他们会看到一个令人生畏的命令列表,按字母顺序排列,这可能并不总是有意义的。 + +为了缓解这种情况, Spring Shell 提供了将命令组合在一起的能力,并提供了合理的默认值。然后,相关的命令将在相同的*集团*(* 例如 *`User Management Commands`)中结束,并一起显示在帮助屏幕和其他位置中。 + +默认情况下,命令将根据它们实现的类进行分组,将 camel case 类名称转换为单独的单词(因此`URLRelatedCommands`变为`URL Related Commands`)。这是一个非常合理的默认设置,因为相关的命令通常已经在类中了,因为它们需要使用相同的协作对象。 + +但是,如果此行为不适合你,则可以以下方式重写命令的组,按优先级顺序排列: + +* 在`@ShellMethod`注释中指定`group()` + +* 在命令定义的类上放置`@ShellCommandGroup`。这将为该类中定义的所有命令应用该组(除非如上所述被重写) + +* 在包上放置`@ShellCommandGroup`(*via*`package-info.java`)命令。这将适用于包中定义的所有命令(除非如上所述在方法或类级别重写) + +下面是一个简短的例子: + +``` +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() {} +} +``` + +### 内置命令 + +任何使用`spring-shell-starter`工件(或者更准确地说,使用`spring-shell-standard-commands`依赖关系)构建的应用程序都带有一组内置命令。这些命令可以单独重写或禁用(参见[覆盖或禁用内置命令](#overriding-or-disabling-built-in-commands)),但是如果它们不是,本节将描述它们的行为。 + +#### 使用`help`命令集成文档 + +运行一个 shell 应用程序通常意味着用户处于一个图形受限的环境中。虽然,在手机时代,我们总是连接在一起,但访问网络浏览器或任何其他丰富的 UI 应用程序(如 PDF 查看器)可能并不总是可能的。这就是为什么 shell 命令被正确地自我记录是很重要的,这就是`help`命令出现的地方。 + +输入`help`+Enter 将列出 shell 中所有已知的命令(包括[不可用](#dynamic-command-availability)命令),并对它们所做的工作进行简短描述: + +``` +shell:>help +AVAILABLE COMMANDS + add: Add numbers together. + * authenticate: Authenticate with the system. + * blow-up: Blow Everything up. + clear: Clear the shell screen. + connect: Connect to the system + disconnect: Disconnect from the system. + exit, quit: Exit the shell. + help: Display help about available commands. + register module: Register a new module. + script: Read and execute commands from a file. + stacktrace: Display the full stacktrace of the last error. + +Commands marked with (*) are currently unavailable. +Type `help ` to learn more. +``` + +键入`help `将显示有关命令的更详细信息,包括可用参数、它们的类型以及它们是否是强制的,*等等。* + +下面是应用于自身的`help`命令: + +``` +shell:>help help + +NAME + help - Display help about available commands. + +SYNOPSYS + help [[-C] string] + +OPTIONS + -C or --command string + The command to obtain help for. [Optional, default = ] +``` + +#### 清除屏幕 + +`clear`命令执行你预期的操作并清除屏幕,重置左上角的提示符。 + +#### 脱壳而出 + +`quit`命令(也别名为`exit`)只需请求 shell 退出,从而优雅地关闭 Spring 应用程序上下文。如果不重写,Jline`History` Bean 将把所有执行的命令的历史记录写入磁盘,以便它们在下次启动时再次可用(参见[与壳相互作用](#interacting-with-the-shell))。 + +#### 显示有关错误的详细信息 + +当命令代码中出现异常时,shell 会捕捉到异常,并显示一条简单的一行消息,以避免过多的信息溢出用户。但在某些情况下,了解到底发生了什么是很重要的(特别是如果异常有一个嵌套的原因)。 + +为此目的, Spring Shell 会记住上次发生的异常,用户以后可以使用`stacktrace`命令在控制台上打印所有血腥的细节。 + +#### 运行一批命令 + +`script`命令接受一个本地文件作为参数,并将重播在该文件中找到的命令,一次一个。 + +从文件中读取的行为与交互式 shell 中的行为完全相同,因此以`//`开头的行将被视为注释并被忽略,而以`\`结尾的行将触发行延续。 + +### 自定义 shell + +#### 覆盖或禁用内置命令 + +[内置命令](#built-in-commands)提供了 Spring shell 以实现许多(如果不是所有的话)shell 应用程序所需的日常任务。如果你对他们的行为方式不满意,你可以禁用或重写他们,就像在本节中解释的那样。 + +| |禁用所有内置命令

如果你根本不需要内置命令,那么有一种简单的方法可以“禁用”它们:只是不要包含它们!
要么在`spring-shell-standard-commands`上使用 Maven 排除,要么,如果你有选择地包括 Spring shell 依赖项,
不要将那个带进来!
<213"/>r=“r=”206"/>| +|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +##### 禁用特定命令 + +要禁用单个内置命令,只需在应用程序`Environment`中将`spring.shell.command..enabled`属性设置为`false`。一种简单的方法是将额外的参数传递到你的`main()`入口点中的引导应用程序: + +``` + public static void main(String[] args) throws Exception { + String[] disabledCommands = {"--spring.shell.command.help.enabled=false"}; (1) + String[] fullArgs = StringUtils.concatenateStringArrays(args, disabledCommands); + SpringApplication.run(MyApp.class, fullArgs); + } +``` + +|**1**|这将禁用集成的`help`命令| +|-----|-------------------------------------------| + +##### 覆盖特定命令 + +如果你不想禁用命令,而是提供自己的实现,那么你可以选择 + +* 禁用上面说明的命令,并以相同的名称注册你的实现。 + +* 让你的实现类实现`.Command`接口。作为示例,下面介绍如何覆盖`clear`命令: + + ``` + public class MyClear implements Clear.Command { + + @ShellMethod("Clear the screen, only better.") + public void clear() { + // ... + } + } + ``` + +| |请考虑贡献你的更改

如果你认为标准命令的实现对社区可能很有价值,
请考虑在[github.com/spring-projects/spring-shell](https://github.com/spring-projects/spring-shell)处打开一个拉-请求。

或者,在你自己进行任何更改之前,你可以打开该项目的一个问题。欢迎反馈
!| +|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +#### 结果解决者 + +#### PromptProvider + +每次调用命令后,shell 都会等待用户的新输入,并以黄色显示*提示*: + +``` +shell:> +``` + +可以通过注册类型`PromptProvider`的 Bean 来定制此行为。 Bean 这样的内部状态可以使用来决定向用户显示什么(它可以例如对[应用程序事件](https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-functionality-events-annotation)做出反应),并且可以使用 Jline 的`AttributedCharSequence`来显示花哨的 ANSI 文本。 + +下面是一个虚构的例子: + +``` +@Component +public class CustomPromptProvider implements PromptProvider { + + private ConnectionDetails connection; + + @Override + public AttributedString getPrompt() { + if (connection != null) { + return new AttributedString(connection.getHost() + ":>", + AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW)); + } + else { + return new AttributedString("server-unknown:>", + AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); + } + } + + @EventListener + public void handle(ConnectionUpdatedEvent event) { + this.connection = event.getConnectionDetails(); + } +} +``` + +#### 自定义命令行选项行为 + +Spring Shell 自带两个默认的 Spring boot`ApplicationRunners`: + +* `InteractiveShellApplicationRunner`引导 shell REPL。它设置了 Jline 基础架构,并最终调用`Shell.run()` + +* `ScriptShellApplicationRunner`查找以`@`开头的程序参数,假设这些参数是本地文件名,并尝试运行这些文件中包含的命令(具有与[脚本命令](#script-command)相同的语义),然后退出进程(通过有效禁用`InteractiveShellApplicationRunner`,见下文)。 + +如果此行为不适合你,只需提供一个(或多个)`ApplicationRunner`类型的 Bean 并可选地禁用标准类型的 Bean。你会想从`ScriptShellApplicationRunner`中获得灵感: + +``` +@Order(InteractiveShellApplicationRunner.PRECEDENCE - 100) // Runs before InteractiveShellApplicationRunner +public class ScriptShellApplicationRunner implements ApplicationRunner { + + @Override + public void run(ApplicationArguments args) throws Exception { + List scriptsToRun = args.getNonOptionArgs().stream() + .filter(s -> s.startsWith("@")) + .map(s -> new File(s.substring(1))) + .collect(Collectors.toList()); + + boolean batchEnabled = environment.getProperty(SPRING_SHELL_SCRIPT_ENABLED, boolean.class, true); + + if (!scriptsToRun.isEmpty() && batchEnabled) { + InteractiveShellApplicationRunner.disable(environment); + for (File file : scriptsToRun) { + try (Reader reader = new FileReader(file); + FileInputProvider inputProvider = new FileInputProvider(reader, parser)) { + shell.run(inputProvider); + } + } + } + } + +... +``` + +#### 自定义参数转换 + +从文本输入到实际方法参数的转换使用标准的 Spring [conversion](https://docs.spring.io/spring/docs/4.3.11.RELEASE/spring-framework-reference/htmlsingle/#core-convert)机制。 Spring Shell 安装一个新的`DefaultConversionService`(启用了内置转换器)并向其寄存器它在应用程序上下文中找到的类型`Converter`、`GenericConverter`或`ConverterFactory`的任何 Bean。 + +这意味着,对类型`Foo`的自定义对象进行自定义转换真的很容易:只需在上下文中安装`Converter` Bean。 + +``` +@ShellComponent +class ConversionCommands { + + @ShellMethod("Shows conversion using Spring converter") + public String conversionExample(DomainObject object) { + return object.getClass(); + } + +} + +class DomainObject { + private final String value; + + DomainObject(String value) { + this.value = value; + } + + public String toString() { + return value; + } +} + +@Component +class CustomDomainConverter implements Converter { + + @Override + public DomainObject convert(String source) { + return new DomainObject(source); + } +} +``` + +| |注意你的字符串表示

就像上面的例子一样,如果你能让
你的`toString()`实现返回创建对象实例时所用的东西的反方向,这可能是个好主意。这是因为当值
验证失败时, Spring shell 打印

```
The following constraints were not met:
--arg : (You passed '')
```

查看[验证命令参数](#validating-command-arguments)获取更多信息。| +|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + +| |如果你想进一步自定义`ConversionService`,你可以

* 在你的代码中插入默认的并以某种方式对其进行操作

* 将其完全覆盖到你自己的(自定义转换器将需要手动注册)。
Spring shell 使用的 ConversionService 需要[qualified](https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/htmlsingle/#beans-autowired-annotation-qualifiers)as`"spring-shell"`。| +|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| \ No newline at end of file