一、SpringBoot整合spring-shell
1、spring-shell介紹
Spring-shell是Spring提供的一個組件,此組件可以將Java中的代碼邏輯封裝為shell命令。通過啟動服務(wù)器上的shell服務(wù)來通過命令方式執(zhí)行java代碼邏輯
2、添加依賴
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
3、聲明shell命令
將java邏輯轉(zhuǎn)換為命令需要@ShellComponent指定類為命令行組件、以及使用@ShellMethod指定方法為命令方法
服務(wù)啟動后界面會出現(xiàn)shell:>,默認情況下方法名稱即使命令名稱,針對大小寫的名稱比如(getName)會將大寫字母進行處理為:get-name
4、demo
官網(wǎng)案例:
package foo;
@ShellComponent
public class TranslationCommands {
private final TranslationService service;
@Autowired
public TranslationCommands(TranslationService service) {
this.service = service;
}
@ShellMethod("Translate text from one language to another.")
public String translate(
@ShellOption(mandatory = true) String text,
@ShellOption(mandatory = true, defaultValue = "en_US") Locale from,
@ShellOption(mandatory = true) Locate to
) {
// invoke service
return service.translate(text, from, to);
}
}
測試demo:
1、spring-shell默認使用方法名稱作為命令的名稱,我們可以在方法的注解中聲明
key屬性來重命名其命令名稱。
2、對于多參數(shù)的方法,我們可以使用參數(shù)順序來和參數(shù)進行一一對應(yīng)也可以使用--argname來對應(yīng)命令中對具體參數(shù)名稱。
3、匹配參數(shù)的--前綴并非是固定的,可以通過在注解中添加prefix="-"來調(diào)整前綴內(nèi)容。
4、針對參數(shù)的校驗:spring-shell使用javax.validation.constraints包中的注解進行校驗。具體可以查看此包中的內(nèi)容來嘗試對命令內(nèi)容進行限制。
5、針對整體狀態(tài)的校驗spring-shell使用Availability提供了另外一種校驗。在connected沒有連接的時候調(diào)用download命令是不合法的。所以在每次調(diào)用download命令時會執(zhí)行命令名稱+Availability的方法,在這個方法中可以根據(jù)執(zhí)行結(jié)果輸出對應(yīng)的錯誤信息。
6、自定義校驗方法除了使用默認的規(guī)則匹配方法,我們還可以主動去指定校驗方法,使用@ShellMethodAvailability注解我們可以指定此命令的校驗方法
7、為校驗指定需要限制的命令除了為命令指定校驗、也可以為校驗指定需要限制的命令。當有多個命令需要接受同一種限制的時候,無需在所有方法中都添加相關(guān)注解,而是可以直接在校驗方法中補充注解關(guān)聯(lián)命令。
8、為命令提供分組help是spring-shell的一個內(nèi)置命令,使用此命令可以查看spring-shell中所有存在的命令。如果我們的命令比較多的時候此時列出來的會是一個長長的清單,這個時候可以使用注釋中的group屬性對命令進行分組
package cn.opendatachain.shell.controller;
import cn.opendatachain.shell.entity.Node;
import cn.opendatachain.shell.service.NodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.Availability;
import org.springframework.shell.standard.ShellCommandGroup;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;
import javax.validation.constraints.Size;
import java.util.List;
/**
* OdcShellTest Description
*
* @author lsj
* @version odc-manage 1.0.0.RELEASE
* <b>Creation Time:</b> 2021/7/29 14:18
*/
@ShellComponent
@ShellCommandGroup("分組的命令")
public class OdcShellTest {
@Autowired
private NodeService nodeService;
/**
* 基礎(chǔ)的命令
* @return
*/
@ShellMethod(value = "輸入兩個整數(shù),獲取相加結(jié)果")
// @ShellMethod("輸入兩個整數(shù),獲取相加結(jié)果")
public List<Node> findAll() {
return nodeService.findAll();
}
/**
* 基礎(chǔ)的命令
* 輸入:add 2 3
* 輸入:sum 2 3
* 輸出:5
* @return
*/
// key 命令名稱
@ShellMethod(value = "輸入兩個整數(shù),獲取相加結(jié)果", key = "sum")
// @ShellMethod("輸入兩個整數(shù),獲取相加結(jié)果")
public int add(int a, int b) {
return a + b;
}
/**
* 多參數(shù) 可以使用 --arg value 指定參數(shù)名稱
* 輸入:echo-int --b 1 --a 2 --c 3
* 輸出:You said a=2, b=1, c=c
*
* 輸入:echo-int 1 2 3
* 輸出:You said a=1, b=2, c=3
* @return
*/
@ShellMethod("通過明明參數(shù)名稱,來指定輸入的數(shù)據(jù)對應(yīng)的參數(shù)名稱")
public String echoInt(int a, int b, int c) {
return String.format("You said a=%d, b=%d, c=%d", a, b, c);
}
/**
*
* 輸入:echo-int2 1 2 3
* 輸出:You said a=1, b=2, c=3
*
* 輸入:echo-int2 -b 2 -a 3 --third 4
* 輸出:You said a=3, b=2, c=4
* @return
*/
@ShellMethod(value = "通過明明參數(shù)名稱,強制的指定輸入的數(shù)據(jù)對應(yīng)的參數(shù)名稱", prefix="-")
public String echoInt2(int a, int b, @ShellOption("--third") int c) {
return String.format("You said a=%d, b=%d, c=%d", a, b, c);
}
/**
* 設(shè)置默認值
* 輸入:echo-string --who ' string is "name"'
* 輸出:input: string is "name"
* @return
*/
@ShellMethod("輸入字符串")
public String echoString(@ShellOption(defaultValue="World") String who) {
return "input:" + who;
}
/**
* 數(shù)組類參數(shù)
* 輸入:echo-array 2 3 4
* 輸出:input:2.0,3.0,4.0
* @return
*/
@ShellMethod("輸入數(shù)組")
public String echoArray(@ShellOption(arity=3) float[] numbers) {
return "input:" + numbers[0] + "," + numbers[1] + "," + numbers[2];
}
/**
* boolean類型參數(shù),boolean 類型參數(shù)當你設(shè)置了參數(shù)會返回true
* 輸入:echo-boolean --force
* 輸出:input:true
*
* 輸入:echo-boolean
* 輸出:input:false
* @return
*/
@ShellMethod("Terminate the system.")
public String echoBoolean(boolean force) {
return "input:" + force;
}
@ShellMethod("只能輸入長度為8至40的內(nèi)容")
public String changePassword(@Size(min = 8, max = 40) String password) {
return "Password successfully set to " + password;
}
private boolean connected;
@ShellMethod("設(shè)置鏈接狀態(tài)為true")
public void connect() {
connected = true;
}
/**
* 輸入:download
* 輸出:
* Command 'download' exists but is not currently available because 沒有進行鏈接
* Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.
*
* 第二次輸入
* 輸入:>connect
* 輸出:>download
*/
@ShellMethod(value = "必須鏈接后才能執(zhí)行的方法",group = "其他組")
public String download() {
System.out.println("123");
return "123";
}
public Availability addAvailability() {
return connected
? Availability.available()
: Availability.unavailable("沒有進行鏈接");
}
}
二、自定義Spring Shell
概述
官網(wǎng):https://projects.spring.io/spring-shell/。
Spring Shell除了提供一些常用的內(nèi)置命令之外,還允許開發(fā)者對一些默認功能進行定制。
自定義內(nèi)置命令
禁用內(nèi)置命令
禁用Spring Shell的內(nèi)置命令非常簡單,只需要在pom.xml文件中進行簡單配置即可,如下所示:
<!-- Spring Shell -->
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>2.0.0.RELEASE</version>
<exclusions>
<!-- 禁用內(nèi)置命令 -->
<exclusion>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-standard-commands</artifactId>
</exclusion>
</exclusions>
</dependency>
shell:>help
No command found for 'help'
shell:>exit
No command found for 'exit'
shell:>
完全禁用了所有內(nèi)置命令之后,將無法通過
help命令查詢其他命令信息,也不能再使用exit命令退出應(yīng)用。
因此,如果有需要的情況下,應(yīng)該只是禁用某些內(nèi)置命令。
如果需要禁用指定內(nèi)置命令,需要在代碼中設(shè)置對應(yīng)的命令屬性為false,格式為:
spring.shell.command.<command>.enabled=true。
例如,需要禁用help命令:
方式一:
@SpringBootApplication
public class TestSpringshellApplication {
public static void main(String[] args) {
String[] disabledCommands = new String[]{"--spring.shell.command.help.enabled=false"};
String[] fullArgs = StringUtils.concatenateStringArrays(args, disabledCommands);
SpringApplication.run(TestSpringshellApplication.class, fullArgs);
}
}
方式二:
spring:
shell:
command:
help:
enabled: false
# help命令將不再能使用
shell:>help
No command found for 'help'
Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.
shell:>exit
如果禁用的是其他命令,如:clear,在Spring Shell應(yīng)用啟動之后通過help命令不再能看被禁用的命令了。
@SpringBootApplication
public class TestSpringshellApplication {
public static void main(String[] args) {
// 禁用了內(nèi)置的clear命令
String[] disabledCommands = new String[]{"--spring.shell.command.clear.enabled=false"};
String[] fullArgs = StringUtils.concatenateStringArrays(args, disabledCommands);
SpringApplication.run(TestSpringshellApplication.class, fullArgs);
}
}
shell:>help
AVAILABLE COMMANDS
Built-In Commands
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.
顯然,在禁用了指定的內(nèi)置命令之后,通過help命令將不能看到該命令了。
覆蓋內(nèi)置命令
如果希望重寫內(nèi)置命令的實現(xiàn),可以通過實現(xiàn)接口org.springframework.shell.standard.commands.<Command>.Command來完成(如:需要重寫clear命令的實現(xiàn),實現(xiàn)接口org.springframework.shell.standard.commands.Clear.Command)。
如下為重寫內(nèi)置命令script的實現(xiàn):
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.commands.Script;
// 實現(xiàn)接口org.springframework.shell.standard.commands.Script.Command
@ShellComponent
public class MyScript implements Script.Command {
// 注意:命令名稱與內(nèi)置命令保持一致
@ShellMethod("Read and execute commands from a file.")
public void script() {
/ // 實現(xiàn)自定義邏輯
System.out.println("override default script command");
}
}
有意思的是,此時在內(nèi)置命令“Built-In Commands”分組中將不能看到script命令了,而是在自定義的分組中,
shell:>help
AVAILABLE COMMANDS
Built-In Commands # 在內(nèi)置命令分組中看不到重寫的命令了
clear: Clear the shell screen.
exit, quit: Exit the shell.
help: Display help about available commands.
stacktrace: Display the full stacktrace of the last error.
My Script # 重寫的命令此時在自定義分組中
scriptdo: Read and execute commands from a file.
如果希望被覆蓋的內(nèi)置命令依然能夠在“Built-In Commands”分組中看到,可以通過注解@ShellMethod的group屬性指定。
// 指定被覆蓋的內(nèi)置命令分組為“Built-In Commands”
@ShellMethod(value = "Read and execute commands from a file.", group = "Built-In Commands")
public void script() {
System.out.println("override default script command");
}
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:>script
override default script command
自定義命令提示符
默認情況下,Spring Shell啟動之后顯示的是一個黃色的命令提示符(shell:>)等待用戶輸入。
可以通過Spring Shell提供的接口org.springframework.shell.jline.PromptProvider對該命令提示符進行定制。
package cn.opendatachain.shell.config;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStyle;
import org.springframework.shell.jline.PromptProvider;
import org.springframework.stereotype.Component;
/**
* OdcPromptProvider Description
*
* @author lishijian
* @version odc-shell 1.0.0.RELEASE
* <b>Creation Time:</b> 2021/7/30 10:00
*/
@Component
public class OdcPromptProvider implements PromptProvider {
@Override
public AttributedString getPrompt() {
// 定制命令提示符為紅色的“odc-shell:>”
return new AttributedString("odc-shell:>",AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
}
}

自定義命令行選項行為
Spring Shell提供了2個默認的ApplicationRunner,用于實現(xiàn)命令行選項的行為。

1、InteractiveShellApplicationRunner用于啟動交互式界面,接收用戶輸入命令。
2、ScriptShellApplicationRunner用于在應(yīng)用啟動時從程序參數(shù)中讀取指定文件中的命令并執(zhí)行,具體來講:將多個命令寫在文件中,并通過參數(shù)的形式將包含了批量命令的文件路徑傳遞給程序,傳遞的文件路徑參數(shù)必須以“@”開始,如下示例:
$ java -jar /home/test/sun/workspace/test-springshell/target/test-springshell-0.0.1-SNAPSHOT.jar @/home/test/cmd
文件/home/test/cmd中的內(nèi)容為:
$ cat /home/test/cmd
help
這樣,在啟動程序時,將會自動執(zhí)行/home/test/cmd文件中的命令(如果文件不存在,啟動應(yīng)用時報錯)。
值得注意的是: 當在程序參數(shù)中存在“@local_file_path”這樣的參數(shù)時,應(yīng)用啟動后執(zhí)行完文件“l(fā)ocal_file_path”內(nèi)命令之后就退出了,不會進入交互式命令行界面(上述示例中,應(yīng)用啟動后執(zhí)行help命令之后就退出了)。
如果Spring Shell默認提供的上述2個ApplicationRunner無法滿足需求,可以自定義其他的命令行選項行為,直接實現(xiàn)接口org.springframework.boot.ApplicationRunner即可。
自定義參數(shù)轉(zhuǎn)換器
默認情況下,Spring Shell使用標準的Spring類型轉(zhuǎn)換機制將命令行的文本參數(shù)轉(zhuǎn)換為指定的類型。
實際上,Spring Shell是通過DefaultConversionService注冊Converter<S, T>,GenericConverter或者ConverterFactory<S, R>類型的Bean對象來實現(xiàn)對命令行參數(shù)進行類型轉(zhuǎn)換的。

換句話說,如果我們需要自定義類型轉(zhuǎn)換器,只需要簡單實現(xiàn)接口org.springframework.core.convert.converter.Converter<S, T>就可以了。

// 自定義類型
public class Food {
private String value = null;
public Food(String value) {
this.value = value;
}
@Override
public String toString() {
return new StringBuilder()
.append("Food{").append("value='").append(value).append("'}")
.toString();
}
}
// 自定義類型轉(zhuǎn)換器
@Component
public class MyConverter implements Converter<String, Food> {
@Override
public Food convert(String s) {
// 將輸入?yún)?shù)轉(zhuǎn)換為Food類型實例
return new Food(s);
}
}
// 使用自定義轉(zhuǎn)換類型
@ShellComponent
public class ConvertionCmd {
// 在命令方法中直接可以獲取Food對象,這是通過前面的自定義類型轉(zhuǎn)換器MyConverter實現(xiàn)的
@ShellMethod("Conversion food")
public String food(Food food) {
return food.toString();
}
}
在命令行指定命令food:
#food apple
Food{value='apple'}
顯然,通過自定義類型轉(zhuǎn)換器可以實現(xiàn)對命令參數(shù)的特殊處理,非常實用。