# 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 (opens new window)。这个最小的应用程序仅依赖于spring-boot-starter并配置spring-boot-maven-plugin,生成一个可执行的 über-jar:

...
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    ...

# 在 Spring shell 上添加依赖项

使用 Spring shell 的最简单方法是依赖于spring-shell-starter工件。这是使用 shell 所需的所有功能,并在引导时很好地发挥作用,只根据需要配置必要的 bean:

...
<dependency>
    <groupId>org.springframework.shell</groupId>
    <artifactId>spring-shell-starter</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>
...
考虑到 Spring shell 将通过存在此依赖关系来启动并启动 REPL,
你将需要在整个教程中构建跳过测试(-DskipTests),或者删除由start.spring.io (opens new window)生成的示例集成测试
。如果不这样做,集成测试将创建
Spring ApplicationContext,并且根据你的构建工具,它将停留在 eval 循环中,或者与 NPE 一起崩溃。

# 你的第一个命令

是时候添加我们的第一个命令了。创建一个新的类(随意命名),并用@ShellComponent@Component的变体,用于限制扫描候选命令的类集)对其进行注释。

然后,创建一个add方法,该方法接受两个 INTS(ab)并返回它们的和。用@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 (opens new window)):

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.6.RELEASE)

shell:>

下面是一个黄色的shell:>提示符,它邀请你键入命令。输入add 1 2然后进入并欣赏魔术!

shell:>add 1 2
3

尝试玩 shell(提示:有一个help命令),完成后,输入exitEnter。

本文的其余部分深入研究了整个 Spring shell 编程模型。

# 编写自己的命令

Spring shell 决定将方法转换为实际的 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)命令的签名:

<br/> @ShellMethod("Describe a command.")<br/> public String help(@ShellOption({"-C", "--command"}) String command) {<br/> ...<br/> }<br/>

# 可选参数和默认值

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
当使用副名参数方法时,应该重复使用不是键。以下是不是的工作:

<br/>shell:>add --numbers 1 --numbers 2 --numbers 3.3<br/>
# 无限性

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
这种特殊处理与默认值规范配合得很好。虽然布尔参数的默认
是将其默认值设置为false,但是你可以另外指定(* 即 *@ShellOption(defaultValue="true")),并且行为将被反转(也就是说,不指定参数
将导致值为true,并且指定该标志将导致值false
具有这种隐式arity()=0的行为可以防止用户指定一个值(* 例如 *shutdown --force true)。
如果你希望允许这种行为(并且放弃该标志方法),则使用注释强制执行1的项:

<br/> @ShellMethod("Terminate the system.")<br/> public String shutdown(@ShellOption(arity=1, defaultValue="false") boolean force) {<br/> return "You said " + force;<br/> }<br/>

# 报价处理

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 (opens new window)库的基础上,因此带来了许多不错的交互特性,其中一些特性将在本节中详细介绍。

首先也是最重要的是, Spring shell 几乎在所有可能的地方都支持选项卡补全。因此,如果有一个echo命令,并且用户按 E,C,Tab,那么echo就会出现。如果有几个以ec开头的命令,那么将提示用户进行选择(使用 tab orshift+tab 进行导航,并输入以进行选择)。

但完成并不止于命令键。如果应用程序开发人员注册了适当的 bean(参见提供 TAB 完成建议),它也适用于参数键(--arg)甚至参数值。

Spring Shell 应用程序的另一个不错的功能是支持行延续。如果一个命令及其参数太长,且不能很好地在屏幕上显示,那么用户可以对其进行分块,并用反斜杠(\)结束一行,然后在下一行按 Enter 并继续。在提交了整个命令后,这将被解析为用户在换行时输入了单个空格。

shell:>register module --type source --name foo  \ (1)
> --uri file:///tmp/bar
Successfully registered module 'source:foo'
1 命令在下一行继续

如果用户打开了一个引号(参见报价处理)并在仍处于引号中时单击 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 (opens new window)集成,以支持对命令参数的自动和自记录约束。

在命令参数上发现的注释以及在方法级别上的注释将得到尊重,并在执行命令之前触发验证。给出以下命令:

        @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

# 动态命令可用性

有时,由于应用程序的内部状态,注册的命令可能没有意义。例如,可能有一个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()属性的默认值是"*",这是一个特殊的
通配符,它匹配所有命令名。因此,只需一个可用性方法,就可以轻松地打开或关闭单个类的
的所有命令。下面是一个例子:

<br/>@ShellComponent<br/>public class Toggles {<br/> @ShellMethodAvailability<br/> public Availability availabilityOnWeekdays() {<br/> return Calendar.getInstance().get(DAY_OF_WEEK) == SUNDAY<br/> ? Availability.available()<br/> : Availability.unavailable("today is not Sunday");<br/> }<br/><br/> @ShellMethod<br/> public void foo() {}<br/><br/> @ShellMethod<br/> public void bar() {}<br/>}<br/>
Spring 对于如何编写命令和如何组织类,shell 并没有施加太多的约束。
但是将相关的命令放在同一个类中通常是很好的实践,可用性指标
可以从中受益。

# 组织命令

当你的 shell 开始提供大量功能时,你可能会使用大量命令,这可能会使你的用户感到困惑。输入help,他们会看到一个令人生畏的命令列表,按字母顺序排列,这可能并不总是有意义的。

为了缓解这种情况, Spring Shell 提供了将命令组合在一起的能力,并提供了合理的默认值。然后,相关的命令将在相同的集团(* 例如 *User Management Commands)中结束,并一起显示在帮助屏幕和其他位置中。

默认情况下,命令将根据它们实现的类进行分组,将 camel case 类名称转换为单独的单词(因此URLRelatedCommands变为URL Related Commands)。这是一个非常合理的默认设置,因为相关的命令通常已经在类中了,因为它们需要使用相同的协作对象。

但是,如果此行为不适合你,则可以以下方式重写命令的组,按优先级顺序排列:

  • @ShellMethod注释中指定group()

  • 在命令定义的类上放置@ShellCommandGroup。这将为该类中定义的所有命令应用该组(除非如上所述被重写)

  • 在包上放置@ShellCommandGroupviapackage-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依赖关系)构建的应用程序都带有一组内置命令。这些命令可以单独重写或禁用(参见覆盖或禁用内置命令),但是如果它们不是,本节将描述它们的行为。

# 使用help命令集成文档

运行一个 shell 应用程序通常意味着用户处于一个图形受限的环境中。虽然,在手机时代,我们总是连接在一起,但访问网络浏览器或任何其他丰富的 UI 应用程序(如 PDF 查看器)可能并不总是可能的。这就是为什么 shell 命令被正确地自我记录是很重要的,这就是help命令出现的地方。

输入help+Enter 将列出 shell 中所有已知的命令(包括不可用命令),并对它们所做的工作进行简短描述:

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 <command>` to learn more.

键入help <command>将显示有关命令的更详细信息,包括可用参数、它们的类型以及它们是否是强制的,等等。

下面是应用于自身的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 = <none>]

# 清除屏幕

clear命令执行你预期的操作并清除屏幕,重置左上角的提示符。

# 脱壳而出

quit命令(也别名为exit)只需请求 shell 退出,从而优雅地关闭 Spring 应用程序上下文。如果不重写,JlineHistory Bean 将把所有执行的命令的历史记录写入磁盘,以便它们在下次启动时再次可用(参见与壳相互作用)。

# 显示有关错误的详细信息

当命令代码中出现异常时,shell 会捕捉到异常,并显示一条简单的一行消息,以避免过多的信息溢出用户。但在某些情况下,了解到底发生了什么是很重要的(特别是如果异常有一个嵌套的原因)。

为此目的, Spring Shell 会记住上次发生的异常,用户以后可以使用stacktrace命令在控制台上打印所有血腥的细节。

# 运行一批命令

script命令接受一个本地文件作为参数,并将重播在该文件中找到的命令,一次一个。

从文件中读取的行为与交互式 shell 中的行为完全相同,因此以//开头的行将被视为注释并被忽略,而以\结尾的行将触发行延续。

# 自定义 shell

# 覆盖或禁用内置命令

内置命令提供了 Spring shell 以实现许多(如果不是所有的话)shell 应用程序所需的日常任务。如果你对他们的行为方式不满意,你可以禁用或重写他们,就像在本节中解释的那样。

禁用所有内置命令

如果你根本不需要内置命令,那么有一种简单的方法可以“禁用”它们:只是不要包含它们!
要么在spring-shell-standard-commands上使用 Maven 排除,要么,如果你有选择地包括 Spring shell 依赖项,
不要将那个带进来!
<gt="><213"/>r=“r=”206"/>
# 禁用特定命令

要禁用单个内置命令,只需在应用程序Environment中将spring.shell.command.<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>.Command接口。作为示例,下面介绍如何覆盖clear命令:

    public class MyClear implements Clear.Command {
    
        @ShellMethod("Clear the screen, only better.")
        public void clear() {
            // ...
        }
    }
    
请考虑贡献你的更改

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

或者,在你自己进行任何更改之前,你可以打开该项目的一个问题。欢迎反馈

# 结果解决者

# PromptProvider

每次调用命令后,shell 都会等待用户的新输入,并以黄色显示提示:

shell:>

可以通过注册类型PromptProvider的 Bean 来定制此行为。 Bean 这样的内部状态可以使用来决定向用户显示什么(它可以例如对应用程序事件 (opens new window)做出反应),并且可以使用 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 bootApplicationRunners:

  • InteractiveShellApplicationRunner引导 shell REPL。它设置了 Jline 基础架构,并最终调用Shell.run()

  • ScriptShellApplicationRunner查找以@开头的程序参数,假设这些参数是本地文件名,并尝试运行这些文件中包含的命令(具有与脚本命令相同的语义),然后退出进程(通过有效禁用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<File> 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 (opens new window)机制。 Spring Shell 安装一个新的DefaultConversionService(启用了内置转换器)并向其寄存器它在应用程序上下文中找到的类型Converter<S, T>GenericConverterConverterFactory<S, T>的任何 Bean。

这意味着,对类型Foo的自定义对象进行自定义转换真的很容易:只需在上下文中安装Converter<String, Foo> 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<String, DomainObject> {

        @Override
        public DomainObject convert(String source) {
                return new DomainObject(source);
        }
}
注意你的字符串表示

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

<br/>The following constraints were not met:<br/> --arg <type> : <message> (You passed '<value.toString()>')<br/>

查看验证命令参数获取更多信息。
如果你想进一步自定义ConversionService,你可以

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

* 将其完全覆盖到你自己的(自定义转换器将需要手动注册)。
Spring shell 使用的 ConversionService 需要qualified (opens new window)as"spring-shell"