提到 Java,大家都會想到 Java 在服務(wù)器端應(yīng)用開發(fā)中的使用。實際上,Java 在命令行應(yīng)用的開發(fā)中也有一席之地。在很多情況下,相對于圖形用戶界面來說,命令行界面響應(yīng)速度快,所占用的系統(tǒng)資源少。在與用戶進(jìn)行交互的場景比較單一時,命令行界面是更好的選擇。命令行界面有其固定的交互模式。通常是由用戶輸入一系列的參數(shù),在執(zhí)行之后把相應(yīng)的結(jié)果在控制臺輸出。命令行應(yīng)用通常需要處理輸入?yún)?shù)的傳遞和驗證、輸出結(jié)果的格式化等任務(wù)。Spring Shell 可以幫助簡化這些常見的任務(wù),讓開發(fā)人員專注于實現(xiàn)應(yīng)用的業(yè)務(wù)邏輯。本文對 Spring Shell 進(jìn)行詳細(xì)的介紹。
Spring Shell 入門
最簡單的創(chuàng)建 Spring Shell 應(yīng)用的方式是使用 Spring Boot。從 Spring Initializr 網(wǎng)站(http://start.spring.io/)上創(chuàng)建一個新的基于 Apache Maven 的 Spring Boot 應(yīng)用,然后添加 Spring Shell 相關(guān)的依賴即可。本文介紹的是 Spring Shell 2.0.0.M2 版本,目前還只是 Milestone 版本,因此需要在 pom.xml 中添加 Spring 提供的包含 Milestone 版本工件的 Maven 倉庫,如代碼清單 1 所示。否則的話,Maven 會無法找到相應(yīng)的工件。
清單 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>
我們接著可以創(chuàng)建第一個基于 Spring Shell 的命令行應(yīng)用。該應(yīng)用根據(jù)輸入的參數(shù)來輸出相應(yīng)的問候語,完整的代碼如清單 3 所示。從代碼清單 3 中可以看到,在 Spring Shell 的幫助下,完整的實現(xiàn)代碼非常簡單。代碼的核心是兩個注解:@ShellComponent 聲明類GreetingApp 是一個 Spring Shell 的組件;@ShellMethod 表示方法 sayHi 是可以在命令行運行的命令。該方法的參數(shù) name 是命令行的輸入?yún)?shù),而其返回值是命令行執(zhí)行的結(jié)果。
清單 3. 輸出問候語的命令行應(yīng)用
dependency>
groupId>org.springframework.shell/groupId>
artifactId>spring-shell-starter/artifactId>
version>2.0.0.M2/version>
/dependency>
接下來我們運行該應(yīng)用。運行起來之后,該應(yīng)用直接進(jìn)入命令行提示界面,我們可以輸入 help 來輸出使用幫助。help 是 Spring Shell 提供的眾多內(nèi)置命令之一,在列出的命令中,可以看到我們創(chuàng)建的 say-hi 命令。我們輸入"say-hi Alex"來運行該命令,可以看到輸出的結(jié)果"Hi Alex"。如果我們直接輸入"say-hi",會看到輸出的錯誤信息,告訴我們參數(shù)"--name"是必須的。從上面的例子可以看出,在 Spring Shell 的幫助下,創(chuàng)建一個命令行應(yīng)用是非常簡單的。很多實用功能都已經(jīng)默認(rèn)提供了。在使用 Spring Initializr 創(chuàng)建的 Spring Boot 項目中,默認(rèn)提供了一個單元測試用例。這個默認(rèn)的單元測試用例與 Spring Shell 在使用時存在沖突。在進(jìn)行代碼清單 3 中的項目的 Maven 構(gòu)建時,該測試用例需要被禁用,否則構(gòu)建過程會卡住。
參數(shù)傳遞與校驗
下面我們討論 Spring Shell 中的參數(shù)傳遞和校驗。Spring Shell 支持兩種不同類型的參數(shù),分別是命名參數(shù)和位置參數(shù)。命名參數(shù)有名稱,可以通過類似--arg 的方式來指定;位置參數(shù)則按照其在方法的參數(shù)列表中的出現(xiàn)位置來進(jìn)行匹配。命名參數(shù)和位置參數(shù)可以混合起來使用,不過命名參數(shù)的優(yōu)先級更高,會首先匹配命名參數(shù)。每個參數(shù)都有默認(rèn)的名稱,與方法中的對應(yīng)的參數(shù)名稱一致。
在代碼清單 4 中的方法有 3 個參數(shù) a、b 和 c。在調(diào)用該命令時,可以使用"echo1 --a 1 --b 2 --c 3",也可以使用"echo1 --a 1 2 3"或"echo1 1 3 --b 2"。其效果都是分別把 1,2 和 3 賦值給 a、b 和 c。
清單 4. 包含多個參數(shù)的命令方法
@ShellMethod("Echo1")
public String echo1(int a, int b, int c) {
return String.format("a = %d, b = %d, c = %d", a, b, c);
}
如果不希望使用方法的參數(shù)名稱作為命令對應(yīng)參數(shù)的名稱,可以通過@ShellOption 來標(biāo)注所要使用的一個或多個參數(shù)名稱。我們可以通過指定多個參數(shù)名稱來提供不同的別名。在代碼清單 5 中,為參數(shù) b 指定了一個名稱 boy??梢酝ㄟ^"echo2 1 --boy 2 3"來調(diào)用。
清單 5. 指定參數(shù)名稱
@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);
}
對于命名參數(shù),默認(rèn)使用的是"--"作為前綴,可以通過@ShellMethod 的屬性 prefix 來設(shè)置不同的前綴。方法對應(yīng)的命令的名稱默認(rèn)是從方法名稱自動得到的,可以通過屬性 key 來設(shè)置不同的名稱,屬性 value 表示的是命令的描述信息。如果參數(shù)是可選的,可以通過@ShellOption 的屬性 defaultValue 來設(shè)置默認(rèn)值。在代碼清單 6 中,我們?yōu)榉椒?withDefault 指定了一個命令名稱 default,同時為參數(shù) value 指定了默認(rèn)值"Hello"。如果直接運行命令"default",輸出的結(jié)果是"Value: Hello";如果運行命令"default 123",則輸出的結(jié)果是"Value: 123"。
清單 6. 指定方法名稱和參數(shù)默認(rèn)值
@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);
}
}
一個參數(shù)可以對應(yīng)多個值。通過@ShellOption 屬性 arity 可以指定一個參數(shù)所對應(yīng)的值的數(shù)量。這些參數(shù)會被添加到一個數(shù)組中,可以在方法中訪問。在代碼清單 7 中,方法 echo3 的參數(shù) numbers 的 arity 值是 3,因此可以映射 3 個參數(shù)。在運行命令"echo3 1 2 3"時,輸出的結(jié)果是"a = 1, b =2, c = 3"。
清單 7. 參數(shù)對應(yīng)多個值
@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]);
}
如果參數(shù)的類型是布爾類型 Boolean,在調(diào)用的時候不需要給出對應(yīng)的值。當(dāng)參數(shù)出現(xiàn)時就表示值為 true。
Spring Shell 支持對參數(shù)的值使用 Bean Validation API 進(jìn)行驗證。比如我們可以用@Size 來限制字符串的長度,用@Min 和@Max 來限制數(shù)值的大小,如代碼清單 8 所示。
清單 8. 校驗參數(shù)
@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);
}
}
結(jié)果處理
Spring Shell 在運行時,內(nèi)部有一個處理循環(huán)。在每個循環(huán)的執(zhí)行過程中,首先讀取用戶的輸入,然后進(jìn)行相應(yīng)的處理,最后再把處理的結(jié)果輸出。這其中的結(jié)果處理是由 org.springframework.shell.ResultHandler 接口來實現(xiàn)的。Spring Shell 中內(nèi)置提供了對于不同類型結(jié)果的處理實現(xiàn)。命令執(zhí)行的結(jié)果可能有很多種:如果用戶輸入的參數(shù)錯誤,輸出的結(jié)果應(yīng)該是相應(yīng)的提示信息;如果在命令的執(zhí)行過程中出現(xiàn)了錯誤,則需要輸出相應(yīng)的錯誤信息;用戶也可能直接退出命令行。Spring Shell 默認(rèn)使用的處理實現(xiàn)是類 org.springframework.shell.result.IterableResultHandler。IterableResultHandler 負(fù)責(zé)處理 Iterable 類型的結(jié)果對象。對于 Iterable 中包含的每個對象,把實際的處理請求代理給另外一個 ResultHandler 來完成。IterableResultHandler 默認(rèn)的代理實現(xiàn)是類 org.springframework.shell.result.TypeHierarchyResultHandler。TypeHierarchyResultHandler 其實是一個復(fù)合的處理器,它會把對于不同類型結(jié)果的 ResultHandler 接口的實現(xiàn)進(jìn)行注冊,然后根據(jù)結(jié)果的類型來選擇相應(yīng)的處理器實現(xiàn)。如果找不到類型完全匹配的處理器實現(xiàn),則會沿著結(jié)果類型的層次結(jié)構(gòu)樹往上查找,直到找到對應(yīng)的處理器實現(xiàn)。Spring Shell 提供了對于 Object 類型結(jié)果的處理實現(xiàn)類 org.springframework.shell.result.DefaultResultHandler,因此所有的結(jié)果類型都可以得到處理。DefaultResultHandler 所做的處理只是把 Object 類型轉(zhuǎn)換成 String,然后輸出到控制臺。
了解了 Spring Shell 對于結(jié)果的處理方式之后,我們可以添加自己所需要的特定結(jié)果類型的處理實現(xiàn)。代碼清單 9 給了一個作為示例的處理結(jié)果類 PrefixedResult。PrefixedResult 中包含一個前綴 prefix 和實際的結(jié)果 result。
清單 9. 帶前綴的處理結(jié)果
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 中,我們?yōu)?PrefixedResult 添加了具體的處理器實現(xiàn)。該實現(xiàn)也非常簡單,只是把結(jié)果按照某個格式進(jìn)行輸出。
清單 10. PrefixedResult 對應(yīng)的處理器實現(xiàn)
@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 中的處理器來進(jìn)行處理,輸出相應(yīng)的結(jié)果。
清單 11. 使用 PrefixedResult 的命令
@ShellComponent
public class CustomResultHandlerApp {
@ShellMethod("Result handler")
public PrefixedResult resultHandler() {
return new PrefixedResult("PRE", "Hello!");
}
}
代碼清單 12 給出了具體的命令運行結(jié)果。
清單 12. 命令的處理結(jié)果
myshell=>result-handler
PRE --> Hello!
自定義提示符
在啟動命令行應(yīng)用時,會發(fā)現(xiàn)該應(yīng)用使用的是默認(rèn)提示符"shell:>"。該提示符是可以定制的,只需要提供接口 org.springframework.shell.jline.PromptProvider 的實現(xiàn)即可。接口 PromptProvider 中只有一個方法,用來返回類型為 org.jline.utils.AttributedString 的提示符。在代碼清單 13 中,我們定義了一個 PromptProvider 接口的實現(xiàn)類,并使用"myshell=>"作為提示符,而且顏色為藍(lán)色。
清單 13. 自定義提示符
@Bean
public PromptProvider promptProvider() {
return () -> new AttributedString("myshell=>",
AttributedStyle.DEFAULT.foreground(AttributedStyle.BLUE));
}
動態(tài)命令可用性
前面所創(chuàng)建的命令都是一直可用的。只要應(yīng)用啟動起來,就可以使用這些命令。不過有些命令的可用性可能取決于應(yīng)用的內(nèi)部狀態(tài),只有內(nèi)部狀態(tài)滿足時,才可以使用這些命令。對于這些命令,Spring Shell 提供了類 org.springframework.shell.Availability 來表示命令的可用性。通過類 Availability 的靜態(tài)方法 available()和 unavailable()來分別創(chuàng)建表示命令可用和不可用的 Availability 對象。
在代碼清單 14 中,我們創(chuàng)建了兩個命令方法 runOnce()和 runAgain()。變量 run 作為內(nèi)部狀態(tài)。在運行 runOnce()之后,變量 run 的值變?yōu)?true。命令 runAgain 的可用性由方法 runAgainAvailability()來確定。該方法根據(jù)變量 run 的值來決定 runAgain 是否可用。按照命名慣例,檢查命令可用性的方法的名稱是在命令方法名稱之后加上 Availability 后綴。如果需要使用不同的方法名稱,或是由一個檢查方法控制多個方法,可以在檢查方法上添加注解@ShellMethodAvailability 來聲明其控制的方法名稱。
清單 14. 動態(tài)命令可用性
@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!");
}
}
輸入?yún)?shù)轉(zhuǎn)換
之前的@ShellMethod 標(biāo)注的方法使用的都是簡單類型的參數(shù)。Spring Shell 通過 Spring 框架的類型轉(zhuǎn)換系統(tǒng)來進(jìn)行參數(shù)類型的轉(zhuǎn)換。Spring 框架已經(jīng)內(nèi)置提供了對常用類型的轉(zhuǎn)換邏輯,包括原始類型、String 類型、數(shù)組類型、集合類型、Java 8 的 Optional 類型、以及日期和時間類型等。我們可以通過 Spring 框架提供的擴展機制來添加自定義的轉(zhuǎn)換實現(xiàn)。
代碼清單 15 中的 User 類是作為示例的一個領(lǐng)域?qū)ο螅?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 用來根據(jù) id 來查找對應(yīng)的 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 實現(xiàn)了 Spring 中的 Converter 接口并添加了從 String 到 User 對象的轉(zhuǎn)換邏輯,即通過 UserService 來進(jìn)行查找。
清單 17. 使用類型轉(zhuǎn)換
@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 的參數(shù)是 User 類型。當(dāng)運行命令"user alex"時,輸入?yún)?shù) alex 會通過代碼清單 17 中的類型轉(zhuǎn)換服務(wù)轉(zhuǎn)換成對應(yīng)的 User 對象,然后輸出 User 對象的屬性值 name。如果找不到與輸入?yún)?shù)值對應(yīng)的 User 對象,則輸出"User not found"。
清單 18. 使用類型轉(zhuǎn)換的命令
@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");
}
}
}
命令組織方式
當(dāng)創(chuàng)建很多個命令時,需要有一種把這些命令組織起來。Spring Shell 提供了不同的方式來對命令進(jìn)行分組。處于同一分組的命令會在 help 命令輸出的幫助中出現(xiàn)在一起。默認(rèn)情況下,同一個類中的命令會被添加到同一分組中。默認(rèn)的分組名稱根據(jù)對應(yīng)的 Java 類名來自動生成。除了默認(rèn)分組之外,還可以顯式的設(shè)置分組??梢允褂聾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 顯示了示例應(yīng)用的 help 命令的輸出結(jié)果,從中可以看到命令的分組情況。
圖 1. 所有的命令列表
![](http://img.jbzj.com/file_images/article/201711/2017112295851397.png?201710229591)
commands.png
內(nèi)置命令
Spring Shell 提供了很多內(nèi)置的命令,如下所示。
運行 help 命令可以列出來應(yīng)用中的所有命令和對應(yīng)的描述信息。
運行 clear 命令可以進(jìn)行清屏操作。
運行 exit 命令可以退出命令行應(yīng)用。
運行 script 命令可以執(zhí)行一個文件中包含的所有命令。
如果不需要某個內(nèi)置命令,可以通過把上下文環(huán)境中的屬性 spring.shell.command.command>.enabled 的值設(shè)為 false 來禁用。如果希望禁用全部的內(nèi)置命令,可以把 spring-shell-standard-commands 從 Maven 依賴中排除,如代碼清單 20 所示。
清單 20. 排除內(nèi)置命令對應(yīng)的 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>
小結(jié)
命令行應(yīng)用以其簡單易用,占有資源少,速度快的特點,仍然在 Java 應(yīng)用開發(fā)中占據(jù)一席之地。Spring Shell 為開發(fā)命令行應(yīng)用提供了堅實的基礎(chǔ),可以極大的提高開發(fā)效率。本文對 Spring Shell 進(jìn)行了詳細(xì)的介紹,從基礎(chǔ)的入門,到參數(shù)的傳遞和校驗,再到結(jié)果處理、自定義提示符、動態(tài)命令可用性、輸入?yún)?shù)轉(zhuǎn)換、命令組織和內(nèi)置命令等。在閱讀本文之后,讀者可以了解如何使用 Spring Shell 開發(fā)命令行應(yīng)用。
以上所述是小編給大家介紹的通過Spring Shell 開發(fā) Java 命令行應(yīng)用,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
您可能感興趣的文章:- 解決java 命令行亂碼的問題
- Intellij IDEA命令行執(zhí)行java無法加載主類解決方案
- Java如何在命令行中獲取指定數(shù)據(jù)
- SpringBoot java-jar命令行啟動原理解析
- C++和Java命令行繪制心形圖案
- Java簡單實現(xiàn)調(diào)用命令行并獲取執(zhí)行結(jié)果示例
- 將java程序打成jar包在cmd命令行下執(zhí)行的方法
- Java命令行下Jar包打包小結(jié)
- java自帶命令行工具jmap、jhat與jinfo的使用實例代碼詳解
- windows命令行中java和javac、javap使用詳解(java編譯命令)
- Java基礎(chǔ)之常用的命令行指令