1. 概述
簡單來説,Spring Shell 項目 提供了一個交互式 shell,用於處理命令並使用 Spring 編程模型構建功能齊全的 CLI。
在本文中,我們將探索其功能、關鍵類和註解,並實現多個自定義命令和定製化。
2. Maven 依賴
首先,我們需要將 spring-shell 依賴添加到我們的 pom.xml 中:
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>最新版本的該製品可在這裏找到。
3. 訪問 Shell
在我們的應用程序中,訪問 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的組件掃描功能:
<beans ... >
<context:component-scan base-package="org.baeldung.shell.simple" />
</beans>一旦組件已由 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.";
}
}請注意,我們可以將多個字符串傳遞給 value 和 key 屬性,分別對應 @CliCommand 和 @CliOption,這允許我們暴露多個命令和參數,它們行為相同。
現在,讓我們檢查一切是否按預期工作:
spring-shell>web-get --url https://www.google.com
<!doctype html ...
spring-shell>web-save --url https://www.google.com --out contents.txt
Done.4.2. 命令可用性
我們可以使用 <em @CliAvailabilityIndicator</em> 註解在返回 boolean 的方法上,在運行時更改命令是否應向 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 command4.4. 默認參數
一個空 key 值對於一個 @CliOption,會使該參數成為默認值。 這樣,我們就能接收來自 shell 的值,這些值不屬於任何命名參數:
@CliOption(key = { "", "url" })現在,我們來檢查它是否按預期工作:
spring-shell>web-get https://www.google.com
<!doctype html ...4.5. 幫助用户
<em @CliCommand</em> 和 <em @CliOption</em> 註解提供 <em help</em> 屬性,允許我們在使用內置的 <em help</em> 命令或按 Tab 鍵進行自動補全時引導用户。
讓我們修改我們的 <em web-get</em> 以添加自定義幫助消息:
@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. 定製化
有三種方法可以自定義 Shell,通過實現 BannerProvider、PromptProvider 和 HistoryFileNameProvider 接口,所有這些接口都已提供默認實現。
此外,我們需要使用 @Order 註解,以便我們的提供者可以優先於這些實現。
讓我們創建一個新的 Banner 以開始我們的定製:
@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(URL):
@Component
public class SimpleURLConverter implements Converter<URL> {
public URL convertFromText(
String value, Class<?> requiredType, String optionContext) {
return new URL(value);
}
public boolean getAllPossibleValues(
List<Completion> 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,根據不同的運行時條件更改命令的可用性,並創建了一個簡單的類型轉換器。