醉卧草庐听风雨

君子藏器于身,待时而动

0%

Spring Shell

日常工作中有时编写的并不是持续运行的服务而是一些一次性处理脚本,这时就可以使用Spring项目中的Spring Shell,顾名思义这是个类似Shell脚本的存在,可以用来快速开发Java的控制台程序。本文将做一些简单的介绍,推荐去Spring Shell的官方文档处学习。

Spring Shell特性

这里直接借用Spring Shell官方文档上的描述,其特性包括以下内容

  • 简单的基于注解的编程模型,可以自定义命令
  • 使用Spring Boot作为基础自动配置插件
  • TAB命令补全,命令颜色定义,执行脚本文件
  • 自定义命令提示符,记录输入的历史命令,结果和错误处理
  • 根据特定条件动态启用命令
  • 与bean验证API集成,实现对参数的校验
  • 内置命令,例如:help, exit

下面将用一个简单的小例子为大家介绍一二,本文相关的源码已上传至码云,有需要的可以对照练习。

简单小例子

Maven依赖

那么就开始领略一下Spring Shell的风采,第一步添加Maven依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<properties>
<spring.shell.version>2.0.1.RELEASE</spring.shell.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<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>${spring.shell.version}</version>
</dependency>
</dependencies>

main方法引导

与普通的Spring Boot程序一样,Spring Shell也需要使用编写一个main方法来启动Spring容器

1
2
3
4
5
6
@SpringBootApplication
public class SpringShellLauncher {
public static void main(String[] args) {
SpringApplication.run(SpringShellLauncher.class, args);
}
}

自定义命令

接着来编写一些自定义的命令,正如官方介绍的那样,使用注解即可

  • @ShellComponent:类注解表明该类是一个shell组件
  • @ShellMethod:方法注解,标识该方法是一个shell方法
  • @ShellMethodAvailability:方法注解
    • 在与@ShellMethod联合使用时表示调用前需要通过@ShellMethodAvailability中方法的校验
    • 单独使用时表示注解中的方法需要调用此方法进行校验
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@ShellComponent
public class CustomCmd {
private boolean connected;

/**
* 简单的方法
*/
@ShellMethod("add")
public int add(int x, int y) {
return x + y;
}

/**
* 对参数进行验证
*/
@ShellMethod("connect sever")
public void connect(@NotEmpty String ip) {
System.out.println("connect to :" + ip);
connected = true;
}

/**
* 需要前置验证且参数需校验
*/
@ShellMethod("send message")
public void chat(@NotEmpty String msg) {
System.out.println("msg :" + msg);
}

/**
* 对chat方法进行可用性检测
*/
@ShellMethodAvailability({"chat"})
public Availability downloadAvailability() {
return connected
? Availability.available()
: Availability.unavailable("server not connected");
}
}

接着使用spring-boot-maven-plugin打包出一个jar,输入命令:java -jar {生成的jar}.jar,这时会相应的进入控制台交互页面,输入help可以得到可用的命令列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
shell:>help
AVAILABLE COMMANDS

Built-In Commands
clear: Clear the shell screen.
exit, quit: Exit the shell.
help: Display help about available commands.
history: Display or save the history of previously run commands
script: Read and execute commands from a file.
stacktrace: Display the full stacktrace of the last error.

Custom Cmd
add: add
* chat: send message
connect: connect sever

Commands marked with (*) are currently unavailable.
Type `help <command>` to learn more.

可以看到Spring Shell的几个内置命令,这几个命令是每个Spring Shell程序都会包含在内的,和三个自定义的命令,简单的介绍下内置的命令

  • clear 清屏
  • exit或quit 退出shell控制台
  • help 显示帮助和可用的方法
  • history 输入的历史命令记录
  • script 从文件中读取命令并执行
  • stacktrace 显示上次错误的堆栈信息

这几个内置命令如果觉得用不上的话可以通过官方的操作说明来自行组织,下面输入自定义的命令可以得到如下验证结果

1
2
3
4
5
6
7
shell:>add 1 1
2
shell:>connect chat.cn
connect to :chat.cn
shell:>chat hello
msg :hello
shell:>

可以看到编写的shell程序正常运行了

批处理模式

在上面的例子中,Spring Shell是以控制台交互模式运行的,但经运维同事提醒一些脚本由于处理时间较长容易出现ssh断开导致执行失败,不过经过查找文档发现Spring Shell在支持使用脚本文件运行。以这个shell为例,编写一段如下的命令列表run.sh

1
2
3
add 1 1
connect chat.cn
chat hello

使用时输java -jar {生成的jar}.jar @{run.sh文件的绝对路径或相对路径},这时就会以非交互模式可以得到相同的运行结果,并且在执行完毕后自动退出。

扩展Spring Shell

虽然Spring Shell支持按脚本非交互运行,但是有时仅仅是一个命令不想写一个命令列表文件,那么该如何处理,这时就需要定制一下Spring Shell的运行方式了,文档介绍了关于自定义运行操作,这里参照ScriptShellApplicationRunner来实现CliApplicationRunner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Order(0)
@Component
public class CliApplicationRunner implements ApplicationRunner {
private final Shell shell;
private final Parser parser;
private final ConfigurableEnvironment environment;


@Autowired
public CliApplicationRunner(Shell shell, Parser parser, ConfigurableEnvironment environment) {
this.shell = shell;
this.parser = parser;
this.environment = environment;
}

@Override
public void run(ApplicationArguments args) throws Exception {
String[] sourceArgs = args.getSourceArgs();
// 参数长度为0时启用控制台模式
if (sourceArgs.length == 0) {
return;
}
// 禁用控制台模式
InteractiveShellApplicationRunner.disable(environment);
// 拼接输入的参数
String argStr = StringUtils.arrayToDelimitedString(sourceArgs, " ");
// 发送参数给shell执行
try {
shell.run(new FileInputProvider(new StringReader(argStr), parser));
} catch (CommandNotFound ex) {
shell.run(new FileInputProvider(new StringReader("help"), parser));
}
}
}

可以看到会检查输入的参数个数,参数个数大于0时禁用控制台交互模式,从而实现java -jar {生成的jar}.jar add 1 1类似这样的单命令模式

写在最后

经过这么一番学习之后可以用java实现一些命令行程序,这里需要感谢运维同事的提醒,不然可能就仅仅简单的使用交互模式,而没有挖出后续的批处理模式和自定义行为模式。希望这篇文章可以帮到各位。