Spring Shell是Spring生態中的一員,用於開發命令行應用程序,官網:https://projects.spring.io/spring-shell/ 。
Spring Shell構建在JLine之上,集成Bean Validation API實現命令參數校驗。
從2.0版本開始,Spring Shell還能夠很是方便地與Spring Boot進行集成,直接使用Spring Boot提供的一些很是實用的功能(如:打包可執行jar文件)。
java
使用Spring Shell很是簡單,直接添加對應的依賴配置便可,而爲了使用Spring Boot提供的便利性,一般都是與Spring Boot集成使用。git
集成Spring Boot本質上就是先新建一個Spring Boot工程,而後添加Spring Shell依賴便可,以下所示:github
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>chench.org.extra</groupId> <artifactId>test-springshell</artifactId> <version>0.0.1-SNAPSHOT</version> <name>test-springshell</name> <description>Test Spring Shell</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- 在Spring Boot項目中添加Spring Shell依賴 --> <dependency> <groupId>org.springframework.shell</groupId> <artifactId>spring-shell-starter</artifactId> <version>2.0.0.RELEASE</version> </dependency> </dependencies> <build> <plugins> <!-- 打包可執行文件 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
添加完上述配置以後,一個基於Spring Boot的使用Spring Shell開發命令行應用程序的基礎開發框架已經搭建完畢,打包運行:正則表達式
$ mvn clean package -Dmaven.test.skip=true $ java -jar test-springshell-0.0.1-SNAPSHOT.jar . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.1.6.RELEASE) 2019-06-21 11:23:54.966 INFO 11286 --- [ main] c.o.e.t.TestSpringshellApplication : Starting TestSpringshellApplication v0.0.1-SNAPSHOT on chench9-pc with PID 11286 (/home/chench9/sun/workspace/test-springshell/target/test-springshell-0.0.1-SNAPSHOT.jar started by chench9 in /home/chench9) 2019-06-21 11:23:54.970 INFO 11286 --- [ main] c.o.e.t.TestSpringshellApplication : No active profile set, falling back to default profiles: default 2019-06-21 11:23:56.457 INFO 11286 --- [ main] c.o.e.t.TestSpringshellApplication : Started TestSpringshellApplication in 2.26 seconds (JVM running for 2.771) shell:>
顯然,使用Spring Shell開發的命令行應用程序與其餘普通應用不一樣,啓動以後停留在命令交互界面,等待用戶輸入。
目前尚未編寫任何與業務相關的代碼,輸入help
命令看看。spring
shell:>help AVAILABLE COMMANDS Built-In Commands clear: Clear the shell screen. exit, quit: Exit the shell. help: Display help about available commands. script: Read and execute commands from a file. stacktrace: Display the full stacktrace of the last error. shell:>
能夠看到,Spring Shell已經內置了一些經常使用的命令,如:help
命令顯示幫助信息,clear
命令清空命令行界面,exit
退出應用。
在交互界面輸出exit
命令退出應用程序。shell
實際上,Spring Shell默認就集成了Spring Boot。
以下,咱們在pom.xml文件只添加Spring Shell依賴配置(不明確配置依賴Spring Boot):apache
<dependencies> <!-- Spring Shell --> <dependency> <groupId>org.springframework.shell</groupId> <artifactId>spring-shell-starter</artifactId> <version>2.0.0.RELEASE</version> </dependency> </dependencies>
項目依賴關係以下圖所示:
數組
按照國際慣例,經過編寫一個簡單的「Hello,World!」程序來介紹Spring Shell的相關概念。app
@ShellComponent public class HelloWorld { @ShellMethod("Say hello") public void hello(String name) { System.out.println("hello, " + name + "!"); } }
如上所示,HellWorld
是一個很是簡單的Java類,在Spring Shell應用中Java類須要使用註解@ShellComponent
來修飾,類中的方法使用註解@ShellMethod
表示爲一個具體的命令。
打包運行,輸入help
命令以後將會看到,默認狀況下在Java類中定義的方法名就是在交互界面中可使用的命令名稱。框架
shell:>help AVAILABLE COMMANDS Built-In Commands clear: Clear the shell screen. exit, quit: Exit the shell. help: Display help about available commands. script: Read and execute commands from a file. stacktrace: Display the full stacktrace of the last error. Hello World # 命令所屬組名 hello: Say hello # 具體的命令 shell:>hello World hello, World! shell:>
至此,一個簡單的基於Spring Shell的命令行交互應用就完成了,下面對Spring Shell中的相關組件進行詳細介紹。
默認狀況下,使用註解@ShellMethod
修飾的Java方法名稱就是具體的交互命令名稱,如上述示例。
追溯註解@ShellMethod
源碼:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface ShellMethod { String INHERITED = ""; String[] key() default {}; // 設置命令名稱 String value() default ""; // 設置命名描述 String prefix() default "--"; // 設置命令參數前綴,默認爲「--」 String group() default ""; // 設置命令分組 }
還可使用註解@ShellMethod
的屬性key設置命令名稱(注意:能夠爲一個命令設置多個名稱)。
@ShellComponent public class Calculator { // 爲一個命令指定多個名稱 @ShellMethod(value = "Add numbers.", key = {"sum", "addition"}) public void add(int a, int b) { int sum = a + b; System.out.println(String.format("%d + %d = %d", a, b, sum)); } }
shell:>help AVAILABLE COMMANDS Built-In Commands clear: Clear the shell screen. exit, quit: Exit the shell. help: Display help about available commands. script: Read and execute commands from a file. stacktrace: Display the full stacktrace of the last error. Calculator addition, sum: Add numbers. shell:>addition 1 2 1 + 2 = 3 shell:>sum 1 2 1 + 2 = 3 shell:>sum --a 1 --b 2 # 使用帶命令參數前綴的方式 1 + 2 = 3 shell:>
顯然,使用註解@ShellMethod
的key屬性能夠爲方法指定多個命令名稱,並且,此時方法名再也不是可用的命令了。
除了能夠自定義命令名稱,還能夠自定義命令參數前綴(默認爲「--」)和命令分組(默認爲命令對應Java方法所在的類名稱)。
// 1.使用屬性value定義命令描述 // 2.使用屬性key定義命令名稱 // 3.使用屬性prefix定義參數前綴 // 4.使用屬性group定義命令分組 @ShellMethod(value = "Add numbers.", key = {"sum", "addition"}, prefix = "-", group = "Cal") public void add(int a, int b) { int sum = a + b; System.out.println(String.format("%d + %d = %d", a, b, sum)); }
以下爲自定義了註解@ShellMethod
各個屬性以後的結果:
shell:>help AVAILABLE COMMANDS Built-In Commands clear: Clear the shell screen. exit, quit: Exit the shell. help: Display help about available commands. script: Read and execute commands from a file. stacktrace: Display the full stacktrace of the last error. Cal addition, sum: Add numbers. shell:>sum --a 1 --b 2 Too many arguments: the following could not be mapped to parameters: '--b 2' Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace. shell:>sum -a 1 -b 2 1 + 2 = 3 shell:>
顯然,命令分組爲自定義的「Cal」,命令參數前綴爲自定義的「-」(此時將不能再使用默認的參數前綴「--」)。
註解@ShellMethod
應用在Java方法上對命令進行定製,還可使用註解@ShellOption
對命令參數進行定製。
@ShellMethod("Echo params") public void echo(int a, int b, @ShellOption("--third") int c) { System.out.println(String.format("a=%d, b=%d, c=%d", a, b, c)); }
如上所示,使用註解@ShellOption
爲第三個參數指定名稱爲「third」。
shell:>echo 1 2 3 a=1, b=2, c=3 shell:>echo --a 1 --b 2 --c 3 # 顯然,當明確指定了參數名稱以後,必須使用指定的名稱 Too many arguments: the following could not be mapped to parameters: '3' Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace. shell:>echo --a 1 --b 2 --third 3 a=1, b=2, c=3 shell:>
使用註解@ShellOption
還能夠爲命令參數指定多個名稱:
@ShellMethod("Echo command help") public void myhelp(@ShellOption({"-C", "--command"}) String cmd) { System.out.println(cmd); }
shell:>myhelp action action shell:>myhelp -C action action shell:>myhelp --command action action
還可使用註解@ShellOption
經過屬性「defaultValue」爲參數指定默認值。
@ShellMethod("Say hello") public void hello(@ShellOption(defaultValue = "World") String name) { System.out.println("hello, " + name + "!"); }
shell:>hello # 顯然,當參數值爲空時使用默認值 hello, World! shell:>hello zhangsan hello, zhangsan!
一般,一個命令參數只對應一個值,若是但願爲一個參數傳遞多個值(對應Java中的數組或集合),可使用註解@ShellOption
的屬性arity指定參數值的個數。
// 參數爲一個數組 @ShellMethod("Add by array") public void addByArray(@ShellOption(arity = 3) int[] numbers) { int sum = 0; for(int number : numbers) { sum += number; } System.out.println(String.format("sum=%d", sum)); }
shell:>add-by-array 1 2 3 sum=6 shell:>add-by-array --numbers 1 2 3 sum=6 shell:>add-by-array --numbers 1 2 3 4 # 傳遞的參數個數超過arity屬性值時報錯 Too many arguments: the following could not be mapped to parameters: '4' Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.
// 參數爲集合 @ShellMethod("Add by list") public void addByList(@ShellOption(arity = 3) List<Integer> numbers) { int s = 0; for(int number : numbers) { s += number; } System.out.println(String.format("s=%d", s)); }
shell:>add-by-list 1 2 3 s=6 shell:>add-by-list --numbers 1 2 3 s=6 shell:>add-by-list --numbers 1 2 3 4 # 傳遞的參數個數超過arity屬性值時報錯 Too many arguments: the following could not be mapped to parameters: '4' Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.
注意: 傳遞的參數個數不能大於@ShellOption
屬性arity設置的值。
// 參數爲Boolean類型 @ShellMethod("Shutdown action") public void shutdown(boolean shutdown) { System.out.println(String.format("shutdown=%s", shutdown)); }
shell:>shutdown shutdown=false shell:>shutdown --shutdown shutdown=true shell:>shutdown --shutdown true Too many arguments: the following could not be mapped to parameters: 'true' Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.
從上述示例能夠知道,對於布爾類型的參數,默認值爲false,當明確傳遞參數名時,值爲true。
注意: 對於布爾參數值處理比較特別,無需像普通參數同樣傳遞參數值,不然報錯。
Spring Shell使用空格來分割參數,當須要傳遞帶空格的參數時,須要將參數使用引號(單引號或者雙引號)引發來。
// 帶空格的參數須要使用引號引發來 @ShellMethod("Echo.") public void echo(String what) { System.out.println(what); }
shell:>echo "Hello,World!" Hello,World! shell:>echo 'Hello,World!' Hello,World! shell:>echo "Hello,\"World!\"" Hello,"World!" shell:>echo '\"Hello,World!\"' "Hello,World!" shell:>echo Hello World # 當參數值中包含空格時,須要使用引號引發來,不然報錯 Too many arguments: the following could not be mapped to parameters: 'World' Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.
Spring Shell集成了Bean Validation API,可用來實現參數校驗。可支持參數校驗的類型不少,如:是否爲空,長度,最大值,最小值等等。
實現參數校驗也是經過註解實現的,經常使用的參數校驗註解有:@Size
(校驗參數長度),@Max
(校驗參數最大值),@Min
(校驗參數最小值),@Pattern
(支持自定義正則表達式校驗規則)。
// 使用@Size註解校驗參數長度 @ShellMethod("Change password") public void changePwd(@Size(min = 6, max = 30) String pwd) { System.out.println(pwd); }
shell:>change-pwd 123 # 當參數長度小於最小值6時報錯 The following constraints were not met: --pwd string : size must be between 6 and 30 (You passed '123') shell:>change-pwd 1234567890123456789012345678901 # 當參數長度大於最大值30時報錯 The following constraints were not met: --pwd string : size must be between 6 and 30 (You passed '1234567890123456789012345678901') shell:>change-pwd 1234567890 # 參數在指定範圍是成功 1234567890
Spring Shell支持的參數註解以下圖所示:
若是存在這樣一種場景:命令A是否能夠執行須要依賴命令B的執行結果,換言之,當命令B的執行結果不知足條件時不容許執行命令A。
Spring Shell針對這個需求也作了支持,翻譯爲:動態命令可用性(Dynamic Command Availability),具體實現有2種方式。
這個概念理解起來有些生硬,簡而言之:命令必須知足特定條件時才能被執行,也就說命令必須知足特定條件纔可用。由於這個「特定條件」是在動態變化的,因此叫作「動態命令可用性」。
爲單一命令提供動態可用性支持經過控制方法命名來實現。
@ShellComponent public class Downloader { private boolean connected = false; @ShellMethod("Connect server") public void connect() { connected = true; } @ShellMethod("Download file") public void download() { System.out.println("Downloaded."); } // 爲命令download提供可用行支持 public Availability downloadAvailability() { return connected ? Availability.available():Availability.unavailable("you are not connected"); } }
shell:>download # download命令依賴connect命令的執行結果,所以在執行connect命令成功以前直接調用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. shell:>connect shell:>download # 在執行命令connect成功以後再執行download命令時成功 Downloaded.
顯然,在這種方式下,必須爲須要實現動態可用性的命令提供一個對應名稱的方法(方法名必須是:「命令名 + Availability」,如:downloadAvailability),且方法的返回值必須爲org.springframework.shell.Availability
對象。
該方式的缺點也很明顯,若是須要實現動態可用性的命令比較多,必須定義同等數量的可用性方法,比較繁瑣。
若是須要爲多個命令提供動態可用性支持,使用註解@ShellMethodAvailability
纔是比較明智的。
而註解@ShellMethodAvailability
的使用方式又有2種:
1.在命令方法上使用@ShellMethodAvailability
指定提供動態可用性支持的方法名
private boolean connected = false; @ShellMethod("Connect server") public void connect() { connected = true; } @ShellMethod("Download") @ShellMethodAvailability({"connectCheck"}) public void download() { System.out.println("Downloaded."); } @ShellMethod("Upload") @ShellMethodAvailability({"connectCheck"}) public void upload() { System.out.println("Uploaded."); } public Availability connectCheck() { return connected ? Availability.available():Availability.unavailable("you are not connected"); }
如上所示,在命令方法download()
和upload()
經過註解@ShellMethodAvailability
指定提供命令動態性實現的方法名:connectCheck,這樣就能夠很方便地實現使用一個方法爲多個命令提供動態可用性支持。
2.直接在提供命令動態可用性支持的方法上使用註解@ShellMethodAvailability
指定命令方法名
另一種實現用一個方法爲多個命令提供動態可用性實現的方式是:直接在命令動態可用性方法上使用註解@ShellMethodAvailability
指定對應的命令方法名。
@ShellMethod("Download") public void download() { System.out.println("Downloaded."); } @ShellMethod("Upload") public void upload() { System.out.println("Uploaded."); } // 直接在提供命令動態可用性的方法上經過註解`@ShellMethodAvailability`指定命令方法名 @ShellMethodAvailability({"download", "upload"}) public Availability connectCheck() { return connected ? Availability.available():Availability.unavailable("you are not connected"); }
1.使用了動態命令可用性的命令會在交互界面中顯示一個星號提示,明確提示該命令的執行須要依賴指定狀態(一般是其餘命令的執行結果)。
Downloader connect: Connect server * download: Download # download和upload命令的執行都須要依賴指定狀態 * upload: Upload # 說明標註星號的命令不可用,能夠經過help命令查看幫助信息 Commands marked with (*) are currently unavailable. Type `help <command>` to learn more. shell:>help download # 經過help命令查看指定命令的幫助信息 NAME download - Download SYNOPSYS download CURRENTLY UNAVAILABLE This command is currently not available because you are not connected.
2.不論如何,提供動態命令可用性的方法返回值必須是org.springframework.shell.Availability
類型對象。
Spring Shell管理命令分組有3種實現方式,分別是:默認以類名爲組名,使用註解@ShellMethod
的group屬性指定組名,使用註解@ShellCommandGroup
指定組名。
命令所在的組爲其對應方法所在的Java類名稱按駝峯法則分隔的名稱(如:「HelloWord」爲類名,則其中的命令組名爲「Hello Word」),這是默認的命令組管理方式。
Hello World # 默認的命令組管理方式 hello: Say hello
經過註解@ShellMethod
的group屬性指定命令所屬的組名
@ShellComponent public class Cmd1 { @ShellMethod(value = "Cmd1 action1", group = "CMD") public void action11() { System.out.println("cmd1 action1"); } @ShellMethod(value = "Cmd1 action2", group = "CMD") public void action12() { System.out.println("cmd1 action2"); } } @ShellComponent public class Cmd2 { @ShellMethod(value = "Cmd2 action1", group = "CMD") public void action21() { System.out.println("cmd2 action1"); } @ShellMethod(value = "Cmd2 action2", group = "CMD") public void action22() { System.out.println("cmd2 action2"); } }
shell:>help AVAILABLE COMMANDS CMD action11: Cmd1 action1 action12: Cmd1 action2 action21: Cmd2 action1 action22: Cmd2 action2
顯然,使用註解@ShellMethod
的group屬性能夠將不一樣類的不一樣命令指定到同一個命令組下。
在使用註解@ShellCommandGroup
指定命令分組時有2種方法:
方法一: 在類上使用註解@ShellCommandGroup
指定組名,則該類下的全部命令都屬於該組
@ShellComponent @ShellCommandGroup("CMD") public class Cmd1 { @ShellMethod(value = "Cmd1 action1") public void action11() { System.out.println("cmd1 action1"); } @ShellMethod(value = "Cmd1 action2") public void action12() { System.out.println("cmd1 action2"); } } @ShellComponent @ShellCommandGroup("CMD") public class Cmd2 { @ShellMethod(value = "Cmd2 action1") public void action21() { System.out.println("cmd2 action1"); } @ShellMethod(value = "Cmd2 action2") public void action22() { System.out.println("cmd2 action2"); } }
# 使用註解@ShellCommandGroup將多個類中的命令指定到一個組下 shell:>help AVAILABLE COMMANDS CMD action11: Cmd1 action1 action12: Cmd1 action2 action21: Cmd2 action1 action22: Cmd2 action2
方法二: 在package-info.java
中使用註解@ShellCommandGroup
指定整個包下的全部類中的命令爲一個組。
以下圖所示,Cmd1.java和Cmd2.java都在包chench.org.extra.testspringshell.group
下,package-info.java
爲對應的包描述類。
Cmd1.java:
package chench.org.extra.testspringshell.group; import org.springframework.shell.standard.ShellComponent; import org.springframework.shell.standard.ShellMethod; @ShellComponent public class Cmd1 { @ShellMethod(value = "Cmd1 action1") public void action11() { System.out.println("cmd1 action1"); } @ShellMethod(value = "Cmd1 action2") public void action12() { System.out.println("cmd1 action2"); } }
Cmd2.java
package chench.org.extra.testspringshell.group; import org.springframework.shell.standard.ShellComponent; import org.springframework.shell.standard.ShellMethod; @ShellComponent public class Cmd2 { @ShellMethod(value = "Cmd2 action1") public void action21() { System.out.println("cmd2 action1"); } @ShellMethod(value = "Cmd2 action2") public void action22() { System.out.println("cmd2 action2"); } }
package-info.java:
// 在Java包描述類中經過註解`@ShellCommandGroup`爲該包下的全部類中的命令指定統一組名 @ShellCommandGroup("CMD") package chench.org.extra.testspringshell.group; import org.springframework.shell.standard.ShellCommandGroup;
shell:>help AVAILABLE COMMANDS CMD action11: Cmd1 action1 action12: Cmd1 action2 action21: Cmd2 action1 action22: Cmd2 action2
注意: 經過註解@ShellCommandGroup
指定的命令分組能夠被註解@ShellMethod
的group屬性指定的組名覆蓋。
Spring Shell提供了5個內置命令:
shell:>help AVAILABLE COMMANDS Built-In Commands clear: Clear the shell screen. # 清空命令行界面 exit, quit: Exit the shell. # 退出應用 help: Display help about available commands. # 顯示幫助信息 script: Read and execute commands from a file. # 從文件中讀取並執行批量命令 stacktrace: Display the full stacktrace of the last error. # 報錯時讀取異常堆棧信息
Spring Shell大大簡化了使用Java開發基於命令行交互應用的步驟,只須要簡單配置,再使用相關注解就能夠開發一個命令行應用了。
同時,Spring Shell還內置了一些有用的命令,如:help
,clear
,stacktrace
,exit
等。
另外,Spring Shell還支持實用TAB鍵補全命令,很是方便。
最後,須要特別注意: Spring Shell不容許出現同名的命令(雖然命令對應的同名方法雖然在不一樣的Java類中被容許,不會出現編譯錯誤,可是運行時將報錯,從而沒法正確啓動應用程序)。即:下面的情形是不容許的。
@ShellComponent public class Cmd1 { @ShellMethod(value = "Cmd1 action1") public void action1() { System.out.println("cmd1 action1"); } @ShellMethod(value = "Cmd1 action2") public void action2() { System.out.println("cmd1 action2"); } } @ShellComponent public class Cmd2 { @ShellMethod(value = "Cmd2 action1") public void action1() { System.out.println("cmd2 action1"); } @ShellMethod(value = "Cmd2 action2") public void action2() { System.out.println("cmd2 action2"); } }
除非使用註解@ShellMethod
的key屬性不一樣的命令指定爲不一樣的名稱,以下所示:
// 使用註解`@ShellMethod`的key屬性不一樣的命令指定爲不一樣的名稱 @ShellComponent public class Cmd1 { @ShellMethod(value = "Cmd1 action1", key = {"cmd11"}) public void action1() { System.out.println("cmd1 action1"); } @ShellMethod(value = "Cmd1 action2", key = {"cmd12"}) public void action2() { System.out.println("cmd1 action2"); } } @ShellComponent public class Cmd2 { @ShellMethod(value = "Cmd2 action1", key = {"cmd21"}) public void action1() { System.out.println("cmd2 action1"); } @ShellMethod(value = "Cmd2 action2", key = {"cmd22"}) public void action2() { System.out.println("cmd2 action2"); } }
shell:>help AVAILABLE COMMANDS CMD cmd11: Cmd1 action1 cmd12: Cmd1 action2 cmd21: Cmd2 action1 cmd22: Cmd2 action2