springBoot整合spring-shell開發(fā)java命令行工具

一、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”分組中看到,可以通過注解@ShellMethodgroup屬性指定。

// 指定被覆蓋的內(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));
    }
}

image

自定義命令行選項行為

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

image

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)換的。

image

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

image
// 自定義類型
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ù)的特殊處理,非常實用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容