提到 Java,大家都會想到 Java 在服務器端應用開發中的使用。實際上,Java 在命令行應用的開發中也有一席之地。在很多情況下,相對于圖形用戶界面來說,命令行界面響應速度快,所占用的系統資源少。在與用戶進行交互的場景比較單一時,命令行界面是更好的選擇。命令行界面有其固定的交互模式。通常是由用戶輸入一系列的參數,在執行之后把相應的結果在控制臺輸出。命令行應用通常需要處理輸入參數的傳遞和驗證、輸出結果的格式化等任務。Spring Shell 可以幫助簡化這些常見的任務,讓開發人員專注于實現應用的業務邏輯。本文對 Spring Shell 進行詳細的介紹。
Spring Shell 入門
最簡單的創建 Spring Shell 應用的方式是使用 Spring Boot。從 Spring Initializr 網站(http://start.spring.io/)上創建一個新的基于 Apache Maven 的 Spring Boot 應用,然后添加 Spring Shell 相關的依賴即可。本文介紹的是 Spring Shell 2.0.0.M2 版本,目前還只是 Milestone 版本,因此需要在 pom.xml 中添加 Spring 提供的包含 Milestone 版本工件的 Maven 倉庫,如代碼清單 1 所示。否則的話,Maven 會無法找到相應的工件。
清單 1. 添加 Spring Shell 的 Maven 倉庫
repositories>
repository>
id>spring-milestone/id>
name>Spring Repository/name>
url>https://repo.spring.io/milestone/url>
/repository>
/repositories>
在添加了 Spring Shell 的 Maven 倉庫之后,可以在 Spring Boot 項目中添加對于spring-shell-starter 的依賴,如代碼清單 2 所示。
清單 2. 添加 Spring Shell 所需 Maven 依賴
dependency>
groupId>org.springframework.shell/groupId>
artifactId>spring-shell-starter/artifactId>
version>2.0.0.M2/version>
/dependency>
我們接著可以創建第一個基于 Spring Shell 的命令行應用。該應用根據輸入的參數來輸出相應的問候語,完整的代碼如清單 3 所示。從代碼清單 3 中可以看到,在 Spring Shell 的幫助下,完整的實現代碼非常簡單。代碼的核心是兩個注解:@ShellComponent 聲明類GreetingApp 是一個 Spring Shell 的組件;@ShellMethod 表示方法 sayHi 是可以在命令行運行的命令。該方法的參數 name 是命令行的輸入參數,而其返回值是命令行執行的結果。
清單 3. 輸出問候語的命令行應用
dependency>
groupId>org.springframework.shell/groupId>
artifactId>spring-shell-starter/artifactId>
version>2.0.0.M2/version>
/dependency>
接下來我們運行該應用。運行起來之后,該應用直接進入命令行提示界面,我們可以輸入 help 來輸出使用幫助。help 是 Spring Shell 提供的眾多內置命令之一,在列出的命令中,可以看到我們創建的 say-hi 命令。我們輸入"say-hi Alex"來運行該命令,可以看到輸出的結果"Hi Alex"。如果我們直接輸入"say-hi",會看到輸出的錯誤信息,告訴我們參數"--name"是必須的。從上面的例子可以看出,在 Spring Shell 的幫助下,創建一個命令行應用是非常簡單的。很多實用功能都已經默認提供了。在使用 Spring Initializr 創建的 Spring Boot 項目中,默認提供了一個單元測試用例。這個默認的單元測試用例與 Spring Shell 在使用時存在沖突。在進行代碼清單 3 中的項目的 Maven 構建時,該測試用例需要被禁用,否則構建過程會卡住。
參數傳遞與校驗
下面我們討論 Spring Shell 中的參數傳遞和校驗。Spring Shell 支持兩種不同類型的參數,分別是命名參數和位置參數。命名參數有名稱,可以通過類似--arg 的方式來指定;位置參數則按照其在方法的參數列表中的出現位置來進行匹配。命名參數和位置參數可以混合起來使用,不過命名參數的優先級更高,會首先匹配命名參數。每個參數都有默認的名稱,與方法中的對應的參數名稱一致。
在代碼清單 4 中的方法有 3 個參數 a、b 和 c。在調用該命令時,可以使用"echo1 --a 1 --b 2 --c 3",也可以使用"echo1 --a 1 2 3"或"echo1 1 3 --b 2"。其效果都是分別把 1,2 和 3 賦值給 a、b 和 c。
清單 4. 包含多個參數的命令方法
@ShellMethod("Echo1")
public String echo1(int a, int b, int c) {
return String.format("a = %d, b = %d, c = %d", a, b, c);
}
如果不希望使用方法的參數名稱作為命令對應參數的名稱,可以通過@ShellOption 來標注所要使用的一個或多個參數名稱。我們可以通過指定多個參數名稱來提供不同的別名。在代碼清單 5 中,為參數 b 指定了一個名稱 boy。可以通過"echo2 1 --boy 2 3"來調用。
清單 5. 指定參數名稱
@ShellMethod("Echo2")
public String echo2(int a, @ShellOption("--boy") int b, int c) {
return String.format("a = %d, b = %d, c = %d", a, b, c);
}
對于命名參數,默認使用的是"--"作為前綴,可以通過@ShellMethod 的屬性 prefix 來設置不同的前綴。方法對應的命令的名稱默認是從方法名稱自動得到的,可以通過屬性 key 來設置不同的名稱,屬性 value 表示的是命令的描述信息。如果參數是可選的,可以通過@ShellOption 的屬性 defaultValue 來設置默認值。在代碼清單 6 中,我們為方法 withDefault 指定了一個命令名稱 default,同時為參數 value 指定了默認值"Hello"。如果直接運行命令"default",輸出的結果是"Value: Hello";如果運行命令"default 123",則輸出的結果是"Value: 123"。
清單 6. 指定方法名稱和參數默認值
@ShellComponent
public class NameAndDefaultValueApp {
@ShellMethod(key = "default", value = "With default value")
public void withDefault(@ShellOption(defaultValue = "Hello") final String value) {
System.out.printf("Value: %s%n", value);
}
}
一個參數可以對應多個值。通過@ShellOption 屬性 arity 可以指定一個參數所對應的值的數量。這些參數會被添加到一個數組中,可以在方法中訪問。在代碼清單 7 中,方法 echo3 的參數 numbers 的 arity 值是 3,因此可以映射 3 個參數。在運行命令"echo3 1 2 3"時,輸出的結果是"a = 1, b =2, c = 3"。
清單 7. 參數對應多個值
@ShellMethod("Echo3")
public String echo3(@ShellOption(arity = 3) int[] numbers) {
return String.format("a = %d, b = %d, c = %d", numbers[0], numbers[1], numbers[2]);
}
如果參數的類型是布爾類型 Boolean,在調用的時候不需要給出對應的值。當參數出現時就表示值為 true。
Spring Shell 支持對參數的值使用 Bean Validation API 進行驗證。比如我們可以用@Size 來限制字符串的長度,用@Min 和@Max 來限制數值的大小,如代碼清單 8 所示。
清單 8. 校驗參數
@ShellComponent
public class ParametersValidationApp {
@ShellMethod("String size")
public String stringSize(@Size(min = 3, max = 16) String name) {
return String.format("Your name is %s", name);
}
@ShellMethod("Number range")
public String numberRange(@Min(10) @Max(100) int number) {
return String.format("The number is %s", number);
}
}
結果處理
Spring Shell 在運行時,內部有一個處理循環。在每個循環的執行過程中,首先讀取用戶的輸入,然后進行相應的處理,最后再把處理的結果輸出。這其中的結果處理是由 org.springframework.shell.ResultHandler 接口來實現的。Spring Shell 中內置提供了對于不同類型結果的處理實現。命令執行的結果可能有很多種:如果用戶輸入的參數錯誤,輸出的結果應該是相應的提示信息;如果在命令的執行過程中出現了錯誤,則需要輸出相應的錯誤信息;用戶也可能直接退出命令行。Spring Shell 默認使用的處理實現是類 org.springframework.shell.result.IterableResultHandler。IterableResultHandler 負責處理 Iterable 類型的結果對象。對于 Iterable 中包含的每個對象,把實際的處理請求代理給另外一個 ResultHandler 來完成。IterableResultHandler 默認的代理實現是類 org.springframework.shell.result.TypeHierarchyResultHandler。TypeHierarchyResultHandler 其實是一個復合的處理器,它會把對于不同類型結果的 ResultHandler 接口的實現進行注冊,然后根據結果的類型來選擇相應的處理器實現。如果找不到類型完全匹配的處理器實現,則會沿著結果類型的層次結構樹往上查找,直到找到對應的處理器實現。Spring Shell 提供了對于 Object 類型結果的處理實現類 org.springframework.shell.result.DefaultResultHandler,因此所有的結果類型都可以得到處理。DefaultResultHandler 所做的處理只是把 Object 類型轉換成 String,然后輸出到控制臺。
了解了 Spring Shell 對于結果的處理方式之后,我們可以添加自己所需要的特定結果類型的處理實現。代碼清單 9 給了一個作為示例的處理結果類 PrefixedResult。PrefixedResult 中包含一個前綴 prefix 和實際的結果 result。
清單 9. 帶前綴的處理結果
public class PrefixedResult {
private final String prefix;
private final String result;
public PrefixedResult(String prefix, String result) {
this.prefix = prefix;
this.result = result;
}
public String getPrefix() {
return prefix;
}
public String getResult() {
return result;
}
}
在代碼清單 10 中,我們為 PrefixedResult 添加了具體的處理器實現。該實現也非常簡單,只是把結果按照某個格式進行輸出。
清單 10. PrefixedResult 對應的處理器實現
@Component
public class PrefixedResultHandler implements ResultHandlerPrefixedResult> {
@Override
public void handleResult(PrefixedResult result) {
System.out.printf("%s --> %s%n", result.getPrefix(), result.getResult());
}
}
在代碼清單 11 中,命令方法 resultHandler 返回的是一個 PrefixedResult 對象,因此會被代碼清單 10 中的處理器來進行處理,輸出相應的結果。
清單 11. 使用 PrefixedResult 的命令
@ShellComponent
public class CustomResultHandlerApp {
@ShellMethod("Result handler")
public PrefixedResult resultHandler() {
return new PrefixedResult("PRE", "Hello!");
}
}
代碼清單 12 給出了具體的命令運行結果。
清單 12. 命令的處理結果
myshell=>result-handler
PRE --> Hello!
自定義提示符
在啟動命令行應用時,會發現該應用使用的是默認提示符"shell:>"。該提示符是可以定制的,只需要提供接口 org.springframework.shell.jline.PromptProvider 的實現即可。接口 PromptProvider 中只有一個方法,用來返回類型為 org.jline.utils.AttributedString 的提示符。在代碼清單 13 中,我們定義了一個 PromptProvider 接口的實現類,并使用"myshell=>"作為提示符,而且顏色為藍色。
清單 13. 自定義提示符
@Bean
public PromptProvider promptProvider() {
return () -> new AttributedString("myshell=>",
AttributedStyle.DEFAULT.foreground(AttributedStyle.BLUE));
}
動態命令可用性
前面所創建的命令都是一直可用的。只要應用啟動起來,就可以使用這些命令。不過有些命令的可用性可能取決于應用的內部狀態,只有內部狀態滿足時,才可以使用這些命令。對于這些命令,Spring Shell 提供了類 org.springframework.shell.Availability 來表示命令的可用性。通過類 Availability 的靜態方法 available()和 unavailable()來分別創建表示命令可用和不可用的 Availability 對象。
在代碼清單 14 中,我們創建了兩個命令方法 runOnce()和 runAgain()。變量 run 作為內部狀態。在運行 runOnce()之后,變量 run 的值變為 true。命令 runAgain 的可用性由方法 runAgainAvailability()來確定。該方法根據變量 run 的值來決定 runAgain 是否可用。按照命名慣例,檢查命令可用性的方法的名稱是在命令方法名稱之后加上 Availability 后綴。如果需要使用不同的方法名稱,或是由一個檢查方法控制多個方法,可以在檢查方法上添加注解@ShellMethodAvailability 來聲明其控制的方法名稱。
清單 14. 動態命令可用性
@ShellComponent
public class RunTwiceToEnableApp {
private boolean run = false;
@ShellMethod("Run once")
public void runOnce() {
this.run = true;
}
@ShellMethod("Run again")
public void runAgain() {
System.out.println("Run!");
}
public Availability runAgainAvailability() {
return run
? Availability.available()
: Availability.unavailable("You should run runOnce first!");
}
}
輸入參數轉換
之前的@ShellMethod 標注的方法使用的都是簡單類型的參數。Spring Shell 通過 Spring 框架的類型轉換系統來進行參數類型的轉換。Spring 框架已經內置提供了對常用類型的轉換邏輯,包括原始類型、String 類型、數組類型、集合類型、Java 8 的 Optional 類型、以及日期和時間類型等。我們可以通過 Spring 框架提供的擴展機制來添加自定義的轉換實現。
代碼清單 15 中的 User 類是作為示例的一個領域對象,包含了 id 和 name 兩個屬性。
清單 15. User
public class User {
private final String id;
private final String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
}
代碼清單 16 中的 UserService 用來根據 id 來查找對應的 User 對象。作為示例,UserService 只是簡單使用一個 HashMap 來保存作為測試的 User 對象。
清單 16. UserService
public class UserService {
private final MapString, User> users = new HashMap>();
public UserService() {
users.put("alex", new User("alex", "Alex"));
users.put("bob", new User("bob", "Bob"));
}
public User findUser(String id) {
return users.get(id);
}
}
在代碼清單 17 中,UserConverter 實現了 Spring 中的 Converter 接口并添加了從 String 到 User 對象的轉換邏輯,即通過 UserService 來進行查找。
清單 17. 使用類型轉換
@Component
public class UserConverter implements ConverterString, User> {
private final UserService userService = new UserService();
@Override
public User convert(String source) {
return userService.findUser(source);
}
}
在代碼清單 18 中,命令方法 user 的參數是 User 類型。當運行命令"user alex"時,輸入參數 alex 會通過代碼清單 17 中的類型轉換服務轉換成對應的 User 對象,然后輸出 User 對象的屬性值 name。如果找不到與輸入參數值對應的 User 對象,則輸出"User not found"。
清單 18. 使用類型轉換的命令
@ShellComponent
public class UserCommandApp {
@ShellMethod("User")
public void user(final User user) {
if (user != null) {
System.out.println(user.getName());
} else {
System.out.println("User not found");
}
}
}
命令組織方式
當創建很多個命令時,需要有一種把這些命令組織起來。Spring Shell 提供了不同的方式來對命令進行分組。處于同一分組的命令會在 help 命令輸出的幫助中出現在一起。默認情況下,同一個類中的命令會被添加到同一分組中。默認的分組名稱根據對應的 Java 類名來自動生成。除了默認分組之外,還可以顯式的設置分組。可以使用@ShellMethod 注解的屬性 group 來指定分組名稱;還可以為包含命令的類添加注解@ShellCommandGroup,則該類中的所有命令都在由@ShellCommandGroup 指定的分組中;還可以把@ShellCommandGroup 注解添加到包聲明中,則該包中的所有命令都在由@ShellCommandGroup 指定的分組中。
在代碼清單 19 中,通過@ShellCommandGroup 為命令所在類添加了自定義的分組名稱 Special。其中的方法 command2 則通過@ShellMethod 的 group 屬性指定了不同的分組名稱"Basic Group"。
清單 19. 組織命令
@ShellComponent
@ShellCommandGroup("Special")
public class CommandsGroupApp {
@ShellMethod("Command1")
public void command1() {}
@ShellMethod(value = "Command2", group = "Basic Group")
public void command2() {}
}
圖 1 顯示了示例應用的 help 命令的輸出結果,從中可以看到命令的分組情況。
圖 1. 所有的命令列表

commands.png
內置命令
Spring Shell 提供了很多內置的命令,如下所示。
運行 help 命令可以列出來應用中的所有命令和對應的描述信息。
運行 clear 命令可以進行清屏操作。
運行 exit 命令可以退出命令行應用。
運行 script 命令可以執行一個文件中包含的所有命令。
如果不需要某個內置命令,可以通過把上下文環境中的屬性 spring.shell.command.command>.enabled 的值設為 false 來禁用。如果希望禁用全部的內置命令,可以把 spring-shell-standard-commands 從 Maven 依賴中排除,如代碼清單 20 所示。
清單 20. 排除內置命令對應的 Maven 依賴
dependency>
groupId>org.springframework.shell/groupId>
artifactId>spring-shell-starter/artifactId>
version>2.0.0.M2/version>
exclusions>
exclusion>
groupId>org.springframework.shell/groupId>
artifactId>spring-shell-standard-commands/artifactId>
/exclusion>
/exclusion>
/dependency>
小結
命令行應用以其簡單易用,占有資源少,速度快的特點,仍然在 Java 應用開發中占據一席之地。Spring Shell 為開發命令行應用提供了堅實的基礎,可以極大的提高開發效率。本文對 Spring Shell 進行了詳細的介紹,從基礎的入門,到參數的傳遞和校驗,再到結果處理、自定義提示符、動態命令可用性、輸入參數轉換、命令組織和內置命令等。在閱讀本文之后,讀者可以了解如何使用 Spring Shell 開發命令行應用。
以上所述是小編給大家介紹的通過Spring Shell 開發 Java 命令行應用,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對腳本之家網站的支持!
您可能感興趣的文章:- 解決java 命令行亂碼的問題
- Intellij IDEA命令行執行java無法加載主類解決方案
- Java如何在命令行中獲取指定數據
- SpringBoot java-jar命令行啟動原理解析
- C++和Java命令行繪制心形圖案
- Java簡單實現調用命令行并獲取執行結果示例
- 將java程序打成jar包在cmd命令行下執行的方法
- Java命令行下Jar包打包小結
- java自帶命令行工具jmap、jhat與jinfo的使用實例代碼詳解
- windows命令行中java和javac、javap使用詳解(java編譯命令)
- Java基礎之常用的命令行指令