1。概述
简而言之,Spring Shell 项目 提供了一个交互式 shell,用于处理命令并使用 Spring 编程模型构建功能齐全的 CLI。
在本文中,我们将探索它的特性、关键类和注释,并实现几个自定义命令和自定义。
2。 Maven 依赖
首先,我们需要将 spring-shell 依赖项添加到 pom.xml 中:
org.springframework.shell
spring-shell
1.2.0.RELEASE
这个神器的最新版本可以在[这里](https://search.maven.org/clas... 22弹簧壳%22)。
3。访问外壳
在我们的应用程序中访问 shell 有两种主要方式。
首先是在我们应用程序的入口点引导 shell 并让用户输入命令:
public static void main(String[] args) throws IOException {
Bootstrap.main(args);
}
第二种是获取 JLineShellComponent 并以编程方式执行命令:
Bootstrap bootstrap = new Bootstrap();
JLineShellComponent shell = bootstrap.getJLineShellComponent();
shell.executeCommand("help");
我们将使用第一种方法,因为它最适合本文中的示例,但是,在源代码中您可以找到使用第二种形式的测试用例。
4。命令
shell 中已经有几个内置命令,例如 clear 、 help 、 exit 等,它们提供了每个 CLI 的标准功能。
可以通过在实现 CommandMarker 接口的 Spring 组件中添加标有 @CliCommand 注释的方法来公开自定义命令。
该方法的每个参数都必须标有 @CliOption 注释,如果我们不这样做,我们将在尝试执行命令时遇到几个错误。
4.1。向 Shell 添加命令
首先,我们需要让 shell 知道我们的命令在哪里。为此,它需要文件 META-INF/spring/spring-shell-plugin.xml 存在于我们的项目中,在那里,我们可以使用 Spring 的组件扫描功能:
一旦组件被 Spring 注册和实例化,它们就会被注册到 shell 解析器,并处理它们的注释。
让我们创建两个简单的命令,一个用于获取 URL 的内容并显示它们,另一个用于将这些内容保存到文件中:
@Component
public class SimpleCLI implements CommandMarker {
@CliCommand(value = { "web-get", "wg" })
public String webGet(
@CliOption(key = "url") String url) {
return getContentsOfUrlAsString(url);
}
@CliCommand(value = { "web-save", "ws" })
public String webSave(
@CliOption(key = "url") String url,
@CliOption(key = { "out", "file" }) String file) {
String contents = getContentsOfUrlAsString(url);
try (PrintWriter out = new PrintWriter(file)) {
out.write(contents);
}
return "Done.";
}
}
请注意,我们可以分别将多个字符串传递给 @CliCommand 和 @CliOption 的 value 和 key 属性,这允许我们公开几个行为相同的命令和参数。
现在,让我们检查一切是否按预期工作:
spring-shell>web-get --url https://www.google.com
web-save --url https://www.google.com --out contents.txt
Done.
4.2。命令的可用性
我们可以在返回 boolean 的方法上使用 @CliAvailabilityIndicator 注释,以在运行时更改命令是否应该暴露给 shell。
首先,让我们创建一个方法来修改 web-save 命令的可用性:
private boolean adminEnableExecuted = false;
@CliAvailabilityIndicator(value = "web-save")
public boolean isAdminEnabled() {
return adminEnableExecuted;
}
现在,让我们创建一个命令来更改 adminEnableExecuted 变量:
@CliCommand(value = "admin-enable")
public String adminEnable() {
adminEnableExecuted = true;
return "Admin commands enabled.";
}
最后,让我们验证一下:
spring-shell>web-save --url https://www.google.com --out contents.txt
Command 'web-save --url https://www.google.com --out contents.txt'
was found but is not currently available
(type 'help' then ENTER to learn about this command)
spring-shell>admin-enable
Admin commands enabled.
spring-shell>web-save --url https://www.google.com --out contents.txt
Done.
4.3。必需的参数
默认情况下,所有命令参数都是可选的。但是,我们可以使用 @CliOption 注释的 mandatory 属性使它们成为必需:
@CliOption(key = { "out", "file" }, mandatory = true)
现在,我们可以测试一下,如果我们不引入它,会导致错误:
spring-shell>web-save --url https://www.google.com
You should specify option (--out) for this command
4.4。默认参数
@CliOption 的空 key 值使该参数成为默认值。在那里,我们将收到不属于任何命名参数的 shell 中引入的值:
@CliOption(key = { "", "url" })
现在,让我们检查它是否按预期工作:
spring-shell>web-get https://www.google.com
4.5。帮助用户
@CliCommand 和 @CliOption 注释提供了一个 help 属性,允许我们在使用内置 help 命令或使用 tab 键获得自动完成时指导我们的用户。
让我们修改我们的 web-get 以添加自定义帮助消息:
@CliCommand(
// ...
help = "Displays the contents of an URL")
public String webGet(
@CliOption(
// ...
help = "URL whose contents will be displayed."
) String url) {
// ...
}
现在,用户可以确切地知道我们的命令做了什么:
spring-shell>help web-get
Keyword: web-get
Keyword: wg
Description: Displays the contents of a URL.
Keyword: ** default **
Keyword: url
Help: URL whose contents will be displayed.
Mandatory: false
Default if specified: '__NULL__'
Default if unspecified: '__NULL__'
* web-get - Displays the contents of a URL.
* wg - Displays the contents of a URL.
5。定制
通过实现 BannerProvider 、 PromptProvider 和 HistoryFileNameProvider 接口,可以通过三种方式自定义 shell,它们都已经提供了默认实现。
此外,我们需要使用 @Order 注释来允许我们的提供者优先于这些实现。
让我们创建一个新横幅来开始我们的定制:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleBannerProvider extends DefaultBannerProvider {
public String getBanner() {
StringBuffer buf = new StringBuffer();
buf.append("=======================================")
.append(OsUtils.LINE_SEPARATOR);
buf.append("* Baeldung Shell *")
.append(OsUtils.LINE_SEPARATOR);
buf.append("=======================================")
.append(OsUtils.LINE_SEPARATOR);
buf.append("Version:")
.append(this.getVersion());
return buf.toString();
}
public String getVersion() {
return "1.0.1";
}
public String getWelcomeMessage() {
return "Welcome to Baeldung CLI";
}
public String getProviderName() {
return "Baeldung Banner";
}
}
请注意,我们还可以更改版本号和欢迎信息。
现在,让我们更改提示:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimplePromptProvider extends DefaultPromptProvider {
public String getPrompt() {
return "baeldung-shell";
}
public String getProviderName() {
return "Baeldung Prompt";
}
}
最后,让我们修改历史文件的名称:
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleHistoryFileNameProvider
extends DefaultHistoryFileNameProvider {
public String getHistoryFileName() {
return "baeldung-shell.log";
}
public String getProviderName() {
return "Baeldung History";
}
}
历史文件将记录在 shell 中执行的所有命令,并将与我们的应用程序放在一起。
一切就绪后,我们可以调用我们的 shell 并查看它的运行情况:
=======================================
* Baeldung Shell *
=======================================
Version:1.0.1
Welcome to Baeldung CLI
baeldung-shell>
6。转换器
到目前为止,我们只使用简单类型作为命令的参数。 Integer 、 Date 、 Enum 、 File 等常见类型已经注册了默认转换器。
通过实现 Converter 接口,我们还可以添加转换器来接收自定义对象。
让我们创建一个可以将 String 转换为 URL 的转换器:
@Component
public class SimpleURLConverter implements Converter {
public URL convertFromText(
String value, Class> requiredType, String optionContext) {
return new URL(value);
}
public boolean getAllPossibleValues(
List completions,
Class> requiredType,
String existingData,
String optionContext,
MethodTarget target) {
return false;
}
public boolean supports(Class> requiredType, String optionContext) {
return URL.class.isAssignableFrom(requiredType);
}
}
最后,让我们修改我们的 web-get 和 web-save 命令:
public String webSave(... URL url) {
// ...
}
public String webSave(... URL url) {
// ...
}
您可能已经猜到了,这些命令的行为相同。
7。小结
在本文中,我们简要介绍了 Spring Shell 项目的核心特性。我们能够贡献我们的命令并使用我们的提供程序自定义 shell,我们根据不同的运行时条件更改了命令的可用性并创建了一个简单的类型转换器。
本文的完整源代码可以在 GitHub 中找到。
本文亦通过 NoOne 的个人博客 发表。