开发者

Spring Shell开发自定义命令行工具详解

目录
  • 1. Spring Shell简介
  • 2. 在SpringBoot项目中集成Spring Shell
    • 添加依赖
    • 基本配置
  • 3. 开发自定义命令
    • 创建基本命令
    • 命令参数配置
  • 4. 高级功能
    • 命令分组
    • 命令可用性控制
    • 自定义提示符
  • 5. 一个工具示例
    • 6. 内置命令清单

      Spring Shell是Spring生态系统中的一个强大组件,它允许开发者轻松创建功能丰富的命令行应用程序。

      当与Spring Boot结合使用时,可以快速构建出专业级别的CLI工具。

      本文将介绍如何在Spring Boot项目中集成和使用Spring Shell,开发自定义命令,以及一些高级特性。

      1. Spring Shell简介

      Spring Shell开发自定义命令行工具详解

      Spring Shell是一个交互式shell框架,它提供了一种通过命令行与应用程序交互的方式。

      它支持自动补全、帮助文档生成、命令历史和各种交互式功能,使命令行工具更加用户友好。

      Spring Shell的主要特性包括:

      • 类似于Bash的交互体验
      • Tab键自动补全功能
      • 内置帮助系统
      • 命令历史记录
      • 参数验证和转换
      • 命令分组和可扩展性

      2. 在SpringBoot项目中集成Spring Shell

      添加依赖

      首先,在您的Spring Boot项目的pom.XML中添加Spring Shell依赖:

      <?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 https://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>3.4.5</version>
              <relativePath/> <!-- lookup parent from repository -->
          </parent>
          <groupId>demo</groupId>
          <artifactId>springboot-cmd</artifactId>
          <version>0.0.1-SNAPSHOT</version>
          <dependencies>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter</artifactId>
              </dependency>
      
              <dependency>
                  <groupId>org.springframework.shell</groupId>
                  <artifactId>spring-shell-starter</artifactId>
                  <version>3.4.0</version>
              </dependency>
      
              <dependency>
                  <groupId>cn.hutool</groupId>
                  <artifactId>hutool-all</artifactId>
                  <version>5.8.18</version>
              </dependency>
      
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <version>1.18.32</version>
                  <scope>provided</scope>
              </dependency>
          </dependencies>
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <configuration>
                          <source>21</source>
                          <target>21</target>
                          <encoding>utf-8</encoding>
                      </configuration>
                  </plugin>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                      <version>3.2.0</version>
                      <executions>
                          <execution>
                              <goals>
                                  <goal>repackage</goal>
                              </goals>
                          </execution>
                      </executions>
                  </plugin>
              </plugins>
          </build>
      
      </project>
      

      基本配置

      @SpringBootApplication
      public class ShellToolApplication {
          public static void main(String[] args) {
              SpringApplication application = new SpringApplication(ShellToolApplication.class);
              application.setBannerMode(Banner.Mode.OFF);
              application.run(args);
          }
      
          @Bean
          public PromptProvider shellPromptProvider() {
              return () -> new AttributedString("boot-shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW));
          }
      
      }
      
      spring:
        shell:
          interactive:
            enabled: true
      

      3. 开发自定义命令

      在Spring Shell中,命令是通过在标有@ShellComponent注解的类中创建标有@ShellMethod注解的方法来定义的。

      创建基本命令

      import org.springframework.shell.standard.ShellComponent;
      import org.springframework.shell.standard.ShellMethod;
      
      @ShellComponent
      public class MyCommands {
          
          @ShellMethod("显示欢迎消息")
          public String hello(String name) {
              return "你好, " + name + "!";
          }
          
          @ShellMethod("计算两个数字的和")
          public int add(int a, int b) {
              return a + b;
          }
      
          // 注意: 如果方法名为驼峰式命名,则shell使用时需要使用-分隔符,如addBig -> add-big
          @ShellMethod(value = "计算两个数字的和,注意大小写")
          public int addBig(int a, int b) {
              return a + b;
          }
      
          // 关于驼峰命名也可以使用key属性进行指定,则shell使用时仍然是驼峰式命名,如addSmall -> addSmall
          @ShellMethod(value = "计算两个数字的和,注意大小写",key = {"addSmall"})
          public int addSmall(int a, int b) {
              return a + b;
          }
      }
      

      使用上面的代码,当您运行应用程序时,将可以使用helloadd命令:

      shell:>hello 世界

      你好, 世界!

      shell:>add 5 3

      8

      命令参数配置

      Spring Shell提供了丰富的参数配置选项:

      import org.springframework.shell.standard.ShellComponent;
      import org.springframework.shell.standard.ShellMethod;
      import org.springframework.shell.standard.ShellOption;
      
      @ShellComponent
      public class AdvancedCommands {
          
          @ShellMethod("使用高级参数选项的示例")
          public String greet(
              @ShellOption(defaultValue = "世界") String name,
              @ShellOption(help = "决定是否使用大写", defaultValue = "false") boolean uppercase
          ) {
              String greeting = "你好, " + name + "!";
              return uppercase ? greeting.toUpperCase() : greeting;
          }
      }
      

      4. 高级功能

      命令分组

      您可以使用@ShellCommandGroup注解对命令进行分组:

      import org.springframework.shell.standard.ShellComponent;
      import org.springframework.shell.standard.ShellMethod;
      import org.springframework.shell.standard.ShellCommandGroup;
      
      @ShellComponent
      @ShellCommandGroup("文件操作")
      public class FileCommands {
          
          @ShellMethod("列出目录内容")
          public String ls(String path) {
              // 实现列出目录内容的逻辑
              return "列出 " + path + " 的内容";
          }
          
          @ShellMethod("创建新目录")
          public String mkdir(String dirName) {
              // 实现创建目录的逻辑
              return "创建目录: " + dirName;
          }
      }
      

      命令可用性控制

      可以使用Availability来控制命令在不同情况下的可用性:

      import org.springframework.shell.Availability;
      import org.springframework.shell.standard.ShellComponent;
      import org.springframework.shell.standard.ShellMethod;
      import org.springframework.shell.standard.ShellMethodAvailability;
      
      @ShellComponent
      public class SecurityCommands {
          
          private boolean loggedIn = false;
          
          @ShellMethod("登录系统")
          public String login(String username, String password) {
              // 实现登录逻辑
              this.loggedIn = true;
              return "用户 " + username + " 已登录";
          }
          
          @ShellMethod("查看敏感信息")
          public String query() {
              return "这是敏感信息,只有登录后才能查看";
          }
          
          @ShellMethodAvailability("query")
          public Availability viewSecretInfoAvailability() {
              return loggedIn
                  ? Availability.available()
                  : Availability.unavailable("您需要先登录才能查看敏感信息");
          }
      }
      

      自定义提示符

      您可以通过实现PromptProvider接口来自定义提示符:

      @Bean
      public PromptProvider shellPromptProvider() {
          return () -> new AttributedString("my-shell:>", AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW));
      }
      

      5. 一个工具示例

      下面是一个更完整的示例,展示如何创建一个简单的HTTP访问CLI工具

      package com.example.cmd.shell;
      
      import cn.hutool.http.HttpRequest;
      import cn.hutool.http.HttpResponse;
      import cn.hutool.http.HttpUtil;
      import cn.hutool.json.JSONUtil;
      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 Java.util.ArrayList;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Mhttp://www.devze.comap;
      
      @ShellComponent
      @ShellCommandGroup("HTTP请求")
      public class HttpClientCommands {
          
          private String baseUrl = "";
          private final Map<String, String> headers = new HashMap<>();
          private final List<String> requestHistory = new ArrayList<>();
          
          @ShellMethod("设置基础URL")
          public String setBaseUrl(String url) {
              this.baseUrl = url;
              return "基础URL已设置为: " + url;
          }
          
          @ShellMethod("添加HTTP请求头")
          public String addHeader(String name, String value) {
              headers.put(name, value);
              return "已添加请求头: " + name + " = " + value;
          }
          
          @ShellMethod("清除所有HTTP请求头")
          public String clearHeaders() {
              int count = headers.size();
              headers.clear();
              return "已清除 " + count + " 编程客栈个请求头";
          }
          
          @ShellMethod("显示当前配置")
          public String showConfig() {
              StringBuilder sb = new StringBuilder();
              sb.append("基础URL: ").append(baseUrl).append("\n");
              sb.append("请求头:\n");
              
              if (headers.isEmpty()) {
                  sb.append("  (无)\n");
              } else {
                  headers.forEach((name, value) -> 
                      sb.append("  ").append(name).append(": ").append(value).append("\n"));
              }
              
              return sb.toString();
          }
          
          @ShellMethod("发送GET请求")
          public String get(
              @ShellOption(help = "请求的路径或完整URL") String path,
              @ShellOption(help = "是否显示响应头", defaultValue = "false") boolean showHeaders
          ) {
              String url = buildUrl(path);
              requestHistory.add("GET " + url);
              
              try {
                  HttpRequest request = HttpRequest.get(url);
                  addHeadersToRequest(request);
                  
                  HttpResponse response = request.execute();
                  return formatResponse(response, showHeaders);
              } catch (Exception e) {
                  return "请求失败: " + e.getMessage();
              }
          }
          
          @ShellMethod("发送POST请求")
          public String post(
              @ShellOption(help = "请求的路径或完整URL") String path,
              @ShellOption(help = "POST请求体(JSON)") String body,
              @ShellOption(help = "是否显示响应头", defaultValue = "false") boolean showHeaders
          ) {
              String url = buildUrl(path);
              requestHistory.add("POST " + url);
              
              try {
                  HttpRequest request = HttpRequest.post(url);
                  addHeadersToRequest(request);
                  
                  // 添加Content-Type请求头,如果没有设置
                  if (!headers.containsKey("Content-Type")) {
                      request.header("Content-Type", "application/json");
                  }
                  
                  HttpResponse response = request.body(body).execute();
                  return formatResponse(response, showHeaders);
              } catch (Exception e) {
                  return "请求失败: " + e.getMessage();
              }
          }
          
          @ShellMethod("发送PUT请求")
          public String put(
              @ShellOption(help = "请求的路径或完整URL") String path,
              @ShellOption(help = "PUT请求体(JSON)") String body,
              @ShellOption(help = "是否显示响应头", defaultValue = "false") boolean showHeaders
          ) {
              String url = buildUrl(path);
              requestHistory.add("PUT " + url);
              
              try {
                  HttpRequest request = HttpRequest.put(url);
                  addHeadersToRequest(request);
                  
                  // 添加Content-Type请求头,如果没有设置
                  if (!headers.containsKey("Content-Type")) {
                      request.header("Content-Type", "application/json");
                  }
                  
                  HttpResponse response = request.body(body).execute();
                  return formatResponse(response, showHeaders);
              } catch (Exception e) {
                  return "请求失败: " + e.getMessage();
              }
          }
          
          @ShellMethod("发送DELETE请求")
          public String delete(
              @ShellOption(help = "请求的路径或完整URL") String path,
              @ShellOption(help = "是否显示响应头", defaultValue = "false") boolean showHeaders
          ) {
              String url = buildUrl(path);
              requestHistory.add("DELETE " + url);
              
              try {
                  HttpRequest request = HttpRequest.delete(url);
                  addHeadersToRequest(request);
                  
                  HttpResponse response = request.execute();
                  return formatResponse(response, showHeaders);
              } catch (Exception e) {
                  return "请求失败: " + e.getMessage();
              }
          }
          
          @ShellMethod("下载文件")
          public String download(
              @ShellOption(help = "文件URL") String url,
              @ShellOption(help = "保存路径") String savePath
          ) {
              requestHistory.add("DOWNLOAD " + url);
              
              try {
                  long fileSize = HttpUtil.downloadFile(url, savePath);
                  return "文件下载成功,大小: " + fileSize + " 字节,保存路径: " + savePath;
              } catch (Exception e) {
                  return "文件下载失败: " + e.getMessage();
              }
          }
          
          @ShellMethod("上传文件")
          public String upload(
              @ShellOption(help = "上传URL") String url,
              @ShellOption(help = "文件参数名") String paramName,
              @ShellOption(help = "文件路径") String filePath,
              @ShellOption(help = "是否显示响应头", defaultValue = "false") boolean showHeaders
          ) {
              requestHistory.add("UPLOAD " + url);
            php  
              try {
                  HttpRequest request = HttpRequest.post(url);
                  addHeadersToRequest(request);
                  
                  request.form(paramName, new java.io.File(filePath));
                  HttpResponse response = request.execute();
                  
                  return formatResponse(response, showHeaders);
              } catch (Exception e) {
                  return "文件上传失败: " + e.getMessage();
              }
          }
          
          @ShellMethod("格式化JSON")
          public String formatJson(
              @ShellOption(help = "JSON字符串") String json
          ) {
              try {
                  return JSONUtil.formatJsonStr(json);
              } catch (Exception e) {
                  return "JSON格式化失败: " + e.getMessage();
              }
          }
          
          private String buildUrl(String path) {
              if (path.toLowerCase().startsWith("http")) {
                  return path;
              }
              
              if (baseUrl.isEmpty()) {
                  return path;
              }
              
              if (baseUrl.endsWith("/") && path.startsWith("/")) php{
                  return baseUrl + path.substring(1);
              } else if (!baseUrl.endsWith("/") && !path.startsWith("/")) {
                  return baseUrl + "/" + path;
              } else {
                  return baseUrl + path;
              }
          }
          
          private void addHeadersToRequest(HttpRequest request) {
              headers.forEach(request::header);
          }
          
          private String formatResponse(HttpResponse response, boolean showHeaders) {
              StringBuilder sb = new StringBuilder();
              sb.append("状态码: ").append(response.getStatus()).append("\n");
              
              if (showHeaders) {
                  sb.append("响应头:\n");
                  response.headers().forEach((name, values) -> {
                      sb.append("  ").append(name).append(": ");
                      sb.append(String.join(", ", values)).append("\n");
                  });
                  sb.append("\n");
              }
              
              sb.append("响应体:\n");
              
              // 尝试格式化JSON响应体
              String body = response.body();
              if (body != null && !body.isEmpty()) {
                  try {
                      if (body.trim().startsWith("{") || body.trim().startsWith("[")) {
                          body = JSONUtil.formatJsonStr(body);
                      }
                  } catch (Exception ignored) {
                      // 如果不是有效的JSON,直接使用原始响应体
                  }
              }
              
              sb.append(body);
              
              return sb.toString();
          }
      }
      

      6. 内置命令清单

      Spring Shell 默认提供以下内置命令,这些命令在应用启动后可直接使用,无需额外配置。各命令功能及典型用法如下

      命令名称功能描述典型用法android
      help查看所有可用命令及其描述(输入 help <命令>可查看特定命令详情)shell:> help``shell:> help add
      clear清空控制台输出(支持快捷键 Ctrl + L)shell:> clear
      exit/ quit退出 Shell 应用(二者功能相同)shell:> exit
      script从文件批量执行命令(需提供绝对路径)shell:> script /tmp/commands.txt
      stacktrace显示最近一次异常的完整堆栈信息(默认只显示简略错误)发生异常后输入:`shell:> stacktrace``
      history显示历史命令shell:> history

      到此这篇关于Spring Shell开发自定义命令行工具详解的文章就介绍到这了,更多相关Spring Shell命令行内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜