Eric BottardJanne ValkealahtiJay BryantCorneil du Plessis
2.1.15
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
本文档的副本可以制作供您自己使用或分发给他人,前提是您不对此类副本收取任何费用,并且每个副本都包含本版权声明,无论是以印刷版还是电子版分发。
1. What is Spring Shell?
1. 什么是Spring Shell?
Not all applications need a fancy web user interface. Sometimes, interacting with an application through an interactive terminal is the most appropriate way to get things done.
并非所有应用程序都需要花哨的 Web 用户界面。有时,通过交互式终端与应用程序交互是完成工作的最合适方式。
Spring Shell lets you create such a runnable application, where the user enters textual commands that are run until the program terminates. The Spring Shell project provides the infrastructure to create such a REPL (Read, Eval, Print Loop) application, letting you concentrate on implementing commands by using the familiar Spring programming model.
Spring Shell 允许您创建这样一个可运行的应用程序,用户在其中输入文本命令,这些命令一直运行到程序终止。Spring Shell 项目提供了创建此类 REPL(读取、评估、打印循环)应用程序的基础结构,让您能够专注于使用熟悉的 Spring 编程模型实现命令。
Spring Shell includes advanced features (such as parsing, tab completion, colorization of output, fancy ASCII-art table display, input conversion, and validation), freeing you to focus on core command logic.
Spring Shell 包括高级功能(例如解析、Tab 自动补全、输出着色、花哨的 ASCII 艺术表显示、输入转换和验证),让您能够专注于核心命令逻辑。
Spring Shell 2.1.x is a major rework to bring the codebase up to date with existing Spring Boot versions, adding new features and, especially, making it work with GraalVM which makes command-line applications much more relevant in a Java space. Moving to a new major version also lets us clean up the codebase and make some needed breaking changes.
Spring Shell 2.1.x 是一项重大的返工,旨在使代码库与现有的 Spring Boot 版本保持同步,添加新功能,特别是使其与 GraalVM 配合使用,这使得命令行应用程序在 Java 空间中更加相关。迁移到新的主要版本还可以让我们清理代码库并进行一些必要的重大更改。
2. Getting Started
2. 入门
To see what Spring Shell has to offer, we can write a trivial shell application that has a simple command to add two numbers.
要了解 Spring Shell 必须提供什么,我们可以编写一个简单的 shell 应用程序,该应用程序具有一个简单的命令来添加两个数字。
2.1. Writing a Simple Boot Application
2.1. 编写一个简单的引导应用程序
Starting with version 2, Spring Shell has been rewritten from the ground up with various enhancements in mind, one of which is easy integration with Spring Boot.
从版本 2 开始,Spring Shell 已经从头开始重写,并考虑了各种增强功能,其中之一是与 Spring Boot 轻松集成。
For the purpose of this tutorial, we create a simple Boot application by using start.spring.io. This minimal application depends only on spring-boot-starter
and configures the spring-boot-maven-plugin
to generate an executable über-jar:
在本教程中,我们将使用 start.spring.io 创建一个简单的 Boot 应用程序。这个最小的应用程序仅依赖于spring-boot-starter,并配置spring-boot-maven-plugin以生成可执行的über-jar:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
2.2. Adding a Dependency on Spring Shell
2.2. 添加对 Spring Shell 的依赖
The easiest way to get going with Spring Shell is to depend on the org.springframework.shell:spring-shell-starter
artifact. This comes with everything you need to use Spring Shell and plays nicely with Boot, configuring only the necessary beans as needed:
使用Spring Shell的最简单方法是依赖于org.springframework.shell:spring-shell-starter工件。这附带了使用 Spring Shell 所需的一切,并且可以很好地与 Boot 配合使用,仅根据需要配置必要的 bean:
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>2.1.15</version>
</dependency>
Given that Spring Shell starts the REPL (Read-Eval-Print-Loop) because this dependency is present, you need to either skip tests when you build (-DskipTests) throughout this tutorial or remove the sample integration test that was generated by start.spring.io. If you do not remove it, the integration test creates the Spring ApplicationContext and, depending on your build tool, stays stuck in the eval loop or crashes with a NPE. 鉴于 Spring Shell 启动 REPL (Read-Eval-Print-Loop) 是因为存在此依赖关系,因此您需要在本教程中构建 (-DskipTests) 时跳过测试,或者删除由 start.spring.io 生成的示例集成测试。如果不删除它,集成测试将创建 Spring ApplicationContext,并且根据您的构建工具,它会停留在 eval 循环中或因 NPE 而崩溃。
2.3. Your First Command
2.3. 你的第一个命令
Now we can add our first command. To do so, create a new class (named whatever you want) and annotate it with @ShellComponent
(a variation of @Component
that is used to restrict the set of classes that are scanned for candidate commands).
现在我们可以添加第一个命令了。为此,请创建一个新类(随心所欲地命名)并使用 @ShellComponent(@Component的变体,用于限制扫描候选命令的类集)对其进行注释。
Then we can create an add
method that takes two ints (a
and b
) and returns their sum. We need to annotate it with @ShellMethod
and provide a description of the command in the annotation (the only piece of information that is required):
然后我们可以创建一个 add 方法,它接受两个整数(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;
}
}
2.4. Trying the Application
2.4. 尝试应用程序
To build the application and run the generated jar, run the following command:
若要生成应用程序并运行生成的 jar,请运行以下命令:
./mvnw clean install -DskipTests
[...]
java -jar target/demo-0.0.1-SNAPSHOT.jar
shell:>
A yellow shell:>
prompt invites you to type commands. Type add 1 2
, press ENTER
, and admire the magic:
黄色的 shell:> 提示符邀请您键入命令。键入 add 1 2,按 ENTER,然后欣赏魔法:
shell:>add --a 1 --b 2
3
You should play with the shell (hint: there is a help
command). When you are done, type exit
and press ENTER
.
The rest of this document delves deeper into the whole Spring Shell programming model.
你应该玩 shell(提示:有一个 help 命令)。完成后,键入 exit 并按 Enter。
本文档的其余部分将更深入地研究整个 Spring Shell 编程模型。
3. Basics
3. 基础知识
This section covers the basics of Spring Shell. Before going on to define actual commands and options, we need to go through some of the fundamental concepts of Spring Shell.
本节介绍 Spring Shell 的基础知识。在继续定义实际的命令和选项之前,我们需要了解 Spring Shell 的一些基本概念。
Essentially, a few things needs to happen before you have a working Spring Shell application:
-
Create a Spring Boot application.
-
Define commands and options.
-
Package the application.
-
Run the application, either interactively or non-interactively.
从本质上讲,在拥有一个有效的 Spring Shell 应用程序之前,需要做一些事情:
- 创建 Spring Boot 应用程序。
- 定义命令和选项。
- 打包应用程序。
- 以交互方式或非交互方式运行应用程序。
You can get a full working Spring Shell application without defining any user-level commands as some basic built-in commands (such as help
and history
) are provided.
您可以获得一个完整的 Spring Shell 应用程序,而无需定义任何用户级命令,因为提供了一些基本的内置命令(例如帮助和历史记录)。
Throughout this documentation, we make references to configuring something by using annotations (mostly relates to use of
@ShellMethod
and@ShellOption
) and to the programmatic way (which usesCommandRegistration
).The programmatic model is how things are actually registered, even if you use annotations. The
@ShellMethod
and@ShellOption
annotations are a legacy feature that we do not yet want to remove.CommandRegistration
is the new development model where new features are added. We are most likely going to replace existing annotations with something better, to support new features in aCommandRegistration
model.在本文档中,我们引用了使用注释(主要与@ShellMethod和@ShellOption的使用有关)和编程方式(使用 CommandRegistration)来配置某些内容。
编程模型是事物的实际注册方式,即使您使用注释也是如此。@ShellMethod 和 @ShellOption 注释是我们尚不想删除的旧功能。CommandRegistration 是添加了新功能的新开发模型。我们很可能会用更好的内容替换现有的注释,以支持 CommandRegistration 模型中的新功能。
4. Commands
4. 命令
In this section, we go through an actual command registration and leave command options and execution for later in a documentation. You can find more detailed info in Command Registration.
在本节中,我们将介绍实际的命令注册,并将命令选项和执行留待以后的文档中。您可以在命令注册中找到更多详细信息。
4.1. Registration
4.1. 注册
There are two different ways to define a command: through an annotation model and through a programmatic model. In the annotation model, you define your methods in a class and annotate the class and the methods with specific annotations. In the programmatic model, you use a more low level approach, defining command registrations (either as beans or by dynamically registering with a command catalog).
定义命令有两种不同的方法:通过注释模型和通过编程模型。在注释模型中,在类中定义方法,并使用特定注释对类和方法进行注释。在编程模型中,使用更低级的方法,定义命令注册(作为 Bean 或通过向命令目录动态注册)。
4.1.1. Annotation Model
4.1.1. 注解模型
When you use the standard API, methods on beans are turned into executable commands, provided that:
-
The bean class bears the
@ShellComponent
annotation. (This is used to restrict the set of beans that are considered.) -
The method bears the
@ShellMethod
annotation.
使用标准 API 时,Bean 上的方法将转换为可执行命令,前提是:
- Bean 类带有@ShellComponent注释。(这用于限制所考虑的 Bean 集。
- 该方法带有@ShellMethod注释。
The @ShellComponent
is a stereotype annotation that is itself meta-annotated with @Component
. As a result, you can use it in addition to the filtering mechanism to declare beans (for example, by using @ComponentScan
).
You can customize the name of the created bean by using the value
attribute of the annotation.
@ShellComponent是一个构造型注释,它本身是用@Component进行元注释的。因此,除了过滤机制之外,您还可以使用它来声明 bean(例如,通过使用 @ComponentScan)。
您可以使用注释的 value 属性来定制创建的 Bean 的名称。
@ShellComponent
static class MyCommands {
@ShellMethod
public void mycommand() {
}
}
The only required attribute of the @ShellMethod
annotation is its value
attribute, which should have a short, one-sentence, description of what the command does. This lets your users get consistent help about your commands without having to leave the shell (see Help).
@ShellMethod 注释的唯一必需属性是其 value 属性,该属性应包含对命令功能的简短描述。这样一来,用户无需离开 shell 即可获得有关命令的一致帮助(请参阅帮助)。
The description of your command should be short — no more than one or two sentences. For better consistency, it should start with a capital letter and end with a period.
命令的描述应该简短——不超过一两句话。为了获得更好的一致性,它应该以大写字母开头,以句点结尾。
By default, you need not specify the key for your command (that is, the word(s) that should be used to invoke it in the shell). The name of the method is used as the command key, turning camelCase names into dashed, gnu-style, names (for example, sayHello()
becomes say-hello
).
默认情况下,您不需要指定命令的键(即,在 shell 中调用命令时应使用的单词)。该方法的名称用作命令键,将 camelCase 名称转换为虚线的 gnu-style, 名称(例如,sayHello() 变为 say-hello)。
You can, however, explicitly set the command key, by using the key
attribute of the annotation:
但是,您可以使用批注的 key 属性显式设置命令键:
@ShellMethod(value = "Add numbers.", key = "sum")
public int add(int a, int b) {
return a + b;
}
The
key
attribute accepts multiple values. If you set multiple keys for a single method, the command is registered with those different aliases.key 属性接受多个值。如果为单个方法设置了多个键,则该命令将使用这些不同的别名进行注册。
The command key can contain pretty much any character, including spaces. When coming up with names though, keep in mind that consistency is often appreciated by users. That is, you should avoid mixing dashed-names with spaced names and other inconsistencies. 命令键几乎可以包含任何字符,包括空格。但是,在提出名称时,请记住,用户通常会欣赏一致性。也就是说,应避免将虚线名称与间隔名称和其他不一致的名称混合在一起。
4.1.2. Programmatic Model
4.1.2. 编程模型
In the programmatic model, CommandRegistration
is defined as a @Bean
, and it is automatically registered:
在编程模型中,CommandRegistration 被定义为@Bean,并自动注册:
@Bean
CommandRegistration commandRegistration() {
return CommandRegistration.builder()
.command("mycommand")
.build();
}
4.2. Organizing Commands
4.2. 组织命令
When your shell starts to provide a lot of functionality, you may end up with a lot of commands, which could be confusing for your users. By typing help
, they would see a daunting list of commands, organized in alphabetical order, which may not always be the best way to show the available commands.
当您的 shell 开始提供大量功能时,您最终可能会收到很多命令,这可能会让您的用户感到困惑。通过键入 help,他们会看到一个令人生畏的命令列表,按字母顺序组织,这可能并不总是显示可用命令的最佳方式。
To alleviate this possible confusion, Spring Shell provides the ability to group commands together, with reasonable defaults. Related commands would then end up in the same group (for example, User Management Commands
) and be displayed together in the help screen and other places.
为了减轻这种可能的混淆,Spring Shell提供了将命令组合在一起的功能,并具有合理的默认值。然后,相关命令将最终位于同一组中(例如,用户管理命令),并一起显示在帮助屏幕和其他位置。
By default, commands are grouped according to the class they are implemented in, turning the camelCase class name into separate words (so URLRelatedCommands
becomes URL Related Commands
). This is a sensible default, as related commands are often already in the class anyway, because they need to use the same collaborating objects.
默认情况下,命令根据实现它们的类进行分组,将 camelCase 类名转换为单独的单词(因此 URLRelatedCommands 变为 URL 相关命令)。这是一个明智的默认值,因为相关命令通常已经存在于类中,因为它们需要使用相同的协作对象。
If, however, this behavior does not suit you, you can override the group for a command in the following ways, in order of priority:
但是,如果此行为不适合您,则可以按优先级顺序通过以下方式覆盖命令的组:
-
Specify a
group()
in the@ShellMethod
annotation. -
Place a
@ShellCommandGroup
on the class in which the command is defined. This applies the group for all commands defined in that class (unless overridden, as explained earlier). -
Place a
@ShellCommandGroup
on the package (throughpackage-info.java
) in which the command is defined. This applies to all the commands defined in the package (unless overridden at the method or class level, as explained earlier).
- 在 @ShellMethod 注解中指定一个 group()。
- 在定义命令的类上放置一个@ShellCommandGroup。这将为该类中定义的所有命令应用该组(除非如前所述被重写)。
- 在定义命令的包上放置一个@ShellCommandGroup(通过 package-info.java)。这适用于包中定义的所有命令(除非在方法或类级别重写,如前所述)。
The following listing shows an example:
下面的清单显示了一个示例:
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.3. Dynamic Command Availability
4.3. 动态命令可用性
Registered commands do not always make sense, due to the internal state of the application. For example, there may be a download
command, but it only works once the user has used connect
on a remote server. Now, if the user tries to use the download
command, the shell should explain that the command exists but that it is not available at the time. Spring Shell lets you do that, even letting you provide a short explanation of the reason for the command not being available.
There are three possible ways for a command to indicate availability. They all use a no-arg method that returns an instance of Availability
. Consider the following example:
由于应用程序的内部状态,已注册的命令并不总是有意义的。例如,可能有一个下载命令,但只有在用户在远程服务器上使用连接后,它才起作用。现在,如果用户尝试使用 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");
}
}
The connect
method is used to connect to the server (details omitted), altering the state of the command through the connected
boolean when done. The download
command as marked as unavailable until the user has connected, thanks to the presence of a method named exactly as the download
command method with the Availability
suffix in its name. The method returns an instance of Availability
, constructed with one of the two factory methods. If the command is not available, an explanation has to be provided. Now, if the user tries to invoke the command while not being connected, here is what happens:
connect 方法用于连接到服务器(省略详细信息),完成后通过连接的布尔值更改命令的状态。在用户连接之前,下载命令标记为不可用,这要归功于存在一个与下载命令方法名称完全相同的方法,其名称中带有可用性后缀。该方法返回 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.命令“download”存在,但当前不可用,因为您未连接。
错误的详细信息已被省略。您可以使用 stacktrace 命令打印完整的堆栈跟踪。
Information about currently unavailable commands is also used in the integrated help. See Help.
有关当前不可用命令的信息也用于集成帮助中。请参阅帮助。
The reason provided when the command is not available should read nicely if appended after “Because”.
You should not start the sentence with a capital or add a final period
如果将命令不可用时提供的原因附加在“因为”之后,则应该很好读。你不应该用大写字母开始句子或添加最后一个句号
If naming the availability method after the name of the command method does not suit you, you can provide an explicit name by using the @ShellMethodAvailability
annotation:
如果在命令方法的名称之后命名可用性方法不适合您,则可以使用 @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");
}
Finally, it is often the case that several commands in the same class share the same internal state and, thus, should all be available or unavailable as a group. Instead of having to stick the @ShellMethodAvailability
on all command methods, Spring Shell lets you flip things around and put the @ShellMethodAvailabilty
annotation on the availability method, specifying the names of the commands that it controls:
最后,通常情况下,同一类中的多个命令共享相同的内部状态,因此,所有命令都应该作为一个组可用或不可用。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");
}
The default value for the
@ShellMethodAvailability.value()
attribute is*
. This special wildcard matches all command names. This makes it easy to turn all commands of a single class on or off with a single availability method:@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 does not impose many constraints on how to write commands and how to organize classes. However, it is often good practice to put related commands in the same class, and the availability indicators can benefit from that. Spring Shell 对如何编写命令和如何组织类没有施加太多限制。但是,将相关命令放在同一个类中通常是很好的做法,可用性指示器可以从中受益。 |
4.4. Exit Code
4.4. 退出代码
Many command line applications when applicable return an exit code which running environment can use to differentiate if command has been executed successfully or not. In a spring-shell
this mostly relates when a command is run on a non-interactive mode meaning one command is always executed once with an instance of a spring-shell
.
许多命令行应用程序(如果适用)会返回退出代码,运行环境可以使用该代码来区分命令是否已成功执行。在spring-shell中,这主要与命令在非交互模式下运行时有关,这意味着一个命令总是与spring-shell的实例一起执行一次。
Default behaviour of an exit codes is as:
-
Errors from a command option parsing will result code of
2
-
Any generic error will result result code of
1
-
Obviously in any other case result code is
0
退出代码的默认行为为:
命令选项解析的错误将导致代码为 2
任何一般错误都将导致结果代码为 1
显然,在任何其他情况下,结果代码都是 0
Every CommandRegistration
can define its own mappings between Exception and exit code. Essentially we’re bound to functionality in Spring Boot
regarding exit code and simply integrate into that.
每个 CommandRegistration 都可以在 Exception 和退出代码之间定义自己的映射。从本质上讲,我们绑定了 Spring Boot 中有关退出代码的功能,并简单地集成到其中。
Assuming there is an exception show below which would be thrown from a command:
假设下面有一个异常显示,该异常将从命令中抛出:
static class MyException extends RuntimeException {
private final int code;
MyException(String msg, int code) {
super(msg);
this.code = code;
}
public int getCode() {
return code;
}
}
It is possible to define a mapping function between Throwable
and exit code. You can also just configure a class to exit code which is just a syntactic sugar within configurations.
可以在 Throwable 和退出代码之间定义映射函数。您也可以只配置一个类来退出代码,这只是配置中的语法糖。
CommandRegistration.builder()
.withExitCode()
.map(MyException.class, 3)
.map(t -> {
if (t instanceof MyException) {
return ((MyException) t).getCode();
}
return 0;
})
.and()
.build();
Exit codes cannot be customized with annotation based configuration 退出代码不能使用基于注释的配置进行自定义 |
5. Options
5. 选项
Command line arguments can be separated into options and positional parameters. Following sections describes features how options are defined and used.
命令行参数可以分为选项和位置参数。以下各节介绍了如何定义和使用选项的功能。
5.1. Definition
5.1. 定义
Options can be defined within a target method as annotations in a method arguments or with programmatically with CommandRegistration
.
可以在目标方法中将选项定义为方法参数中的批注,也可以使用 CommandRegistration 以编程方式定义选项。
Having a target method with argument is automatically registered with a matching argument name.
具有带参数的目标方法会自动注册为匹配的参数名称。
public String example(String arg1) {
return "Hello " + arg1;
}
shell:>example a
Hello a
@ShellOption
annotation can be used to define an option name if you don’t want it to be same as argument name.
如果您不希望选项名称与参数名称相同,则可以使用@ShellOption注释来定义选项名称。
public String example(@ShellOption(value = { "--argx" }) String arg1) {
return "Hello " + arg1;
}
shell:>example --argx a
Hello a
If option name is defined without prefix, either -
or --
, it is discovered from ShellMethod#prefix.
如果定义选项名称时不带前缀,则从 ShellMethod#prefix 中发现 - 或 --,。
public String example(@ShellOption(value = { "argx" }) String arg1) {
return "Hello " + arg1;
}