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是一个交互式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; } }
使用上面的代码,当您运行应用程序时,将可以使用hello
和add
命令:
shell:>hello 世界
你好, 世界!shell:>add 5 38
命令参数配置
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)!
精彩评论