第三章 命令行开发
前言
本笔记主要用途是学习up 主程序员鱼皮的项目:代码生成器时的一些学习心得。
项目教程:www.codefather.cn/course/1790…
上一章节内容:juejin.cn/post/747372…
本节重点
- 第一阶段:开发本地代码生成器。
- 重点内容:
- Java命令行开发方案
- Picocli命令行框架入门
- 命令模式讲解
- Picocli命令行代码生成器开发
一、Java命令行开发方案
- 什么是命令行程序?
- CLI(Command Line Interface):通过终端窗口接收纯文本命令并执行任务。
- 常见环境:Unix/Linux终端、Windows命令提示符、PowerShell等。
- 命令的结构
- command:命令类型(如
generate)。 - option:选项(如
-loop)。 - parameter:参数(如
author yupi)。
- command:命令类型(如
- 为什么要开发命令行?
- 轻量级,不依赖图形界面。
- 可直接在操作系统终端运行。
- 支持用户交互和帮助手册。
- 简单直接,符合程序员使用习惯。
- 命令行的作用
- 与用户交互,引导输入代码生成的定制参数。
- 封装参数为配置对象,传递给代码生成器。
- 实现方案
- 自主实现:使用Java内置类库(如
Scanner)读取输入,但需要手动解析命令和实现交互。 - 第三方库:推荐使用Picocli框架,支持命令解析、交互式输入、帮助手册等功能。
- 自主实现:使用Java内置类库(如
二、Picocli命令行框架入门
- 入门Demo
-
在
pom.xml中引入Picocli依赖:<dependency> <groupId>info.picocli</groupId> <artifactId>picocli</artifactId> <version>4.7.5</version> </dependency> -
示例代码:
@Command(name = "ASCIIArt", version = "ASCIIArt 1.0", mixinStandardHelpOptions = true) public class ASCIIArt implements Runnable { @Option(names = {"-s", "--font-size"}, description = "Font size") int fontSize = 19; @Parameters(paramLabel = "<word>", defaultValue = "Hello, picocli", description = "Words to be translated into ASCII art.") private String[] words = {"Hello,", "picocli"}; @Override public void run() { System.out.println("fontSize = " + fontSize); System.out.println("words = " + String.join(",", words)); } public static void main(String[] args) { int exitCode = new CommandLine(new ASCIIArt()).execute(args); System.exit(exitCode); } }
-
- 实用功能
- 帮助手册:通过
mixinStandardHelpOptions = true自动生成。 - 命令解析:使用
@Option和@Parameters注解解析命令行参数。 - 交互式输入:通过
interactive = true实现。 - 子命令:支持命令嵌套,适用于复杂工具(如
git、docker)。
- 帮助手册:通过
- 更多学习资源
- 官方文档:picocli.info/
- 中文入门教程:blog.csdn.net/it_freshman…
三、命令模式
-
什么是命令模式?
- 将请求封装为对象,解耦请求发送者和接收者。
- 适用于操作队列化、记录操作历史、撤销重做等场景。
-
命令模式的要素
- 命令:定义执行操作的接口(如
execute())。 - 具体命令:实现命令接口,调用接收者的操作。
- 接收者:执行具体操作的对象。
- 调用者:执行命令的对象。
- 客户端:创建命令对象并触发执行。
- 命令:定义执行操作的接口(如
-
示例代码
// 命令接口 public interface Command { void execute(); } // 具体命令 public class TurnOnCommand implements Command { private Device device; public TurnOnCommand(Device device) { this.device = device; } @Override public void execute() { device.turnOn(); } } // 接收者 public class Device { public void turnOn() { System.out.println("设备打开"); } } // 调用者 public class RemoteControl { private Command command; public void setCommand(Command command) { this.command = command; } public void pressButton() { command.execute(); } } // 客户端 public class Client { public static void main(String[] args) { Device tv = new Device(); Command turnOn = new TurnOnCommand(tv); RemoteControl remote = new RemoteControl(); remote.setCommand(turnOn); remote.pressButton(); } }
四、Picocli命令行代码生成器开发
-
需求分析
- 支持3种子命令:
generate:生成代码。list:查看要生成的原始文件列表信息。config:允许用户查看动态参数信息。
- 支持完整命令和交互式输入。
- 支持3种子命令:
-
开发步骤
-
创建命令执行器(
CommandExecutor)。package com.liucc.cli; import com.liucc.cli.command.ConfigCommand; import com.liucc.cli.command.GenerateCommand; import com.liucc.cli.command.ListCommand; import picocli.CommandLine; @CommandLine.Command(name = "yuzi", mixinStandardHelpOptions = true) public class CommandExecutor implements Runnable{ private final CommandLine commandLine; { commandLine = new CommandLine(this) .addSubcommand(new ConfigCommand()) .addSubcommand(new ListCommand()) .addSubcommand(new GenerateCommand()); } @Override public void run() { // 不输入子命令时,给出友好提示 System.out.println("请输入具体命令,或者输入 --help 查看命令"); } /** * 执行命令 * @param args * @return */ public Integer doExecute(String[] args) { return commandLine.execute(args); } } -
实现每种子命令(
GenerateCommand、ListCommand、ConfigCommand)。 -
提供全局调用入口(
Main类)。 -
构建jar包并测试。
-
封装脚本简化使用。
-
-
子命令实现
- generate命令:
- 使用Picocli注解定义参数。
- 调用
MainGenerator类生成代码。
- list命令:
- 遍历
acm-template目录下的文件。
- 遍历
- config命令:
- 使用反射动态打印
MainTemplateConfig类的字段信息。
- 使用反射动态打印
// 配置命令 @CommandLine.Command(name = "config", description = "查看参数信息", mixinStandardHelpOptions = true) public class ConfigCommand implements Runnable { public void run() { // 实现 config 命令的逻辑 System.out.println("查看参数信息"); Field[] fields = ReflectUtil.getFields(MainTemplateConfig.class); // 遍历并打印每个字段的信息 for (Field field : fields) { System.out.println("字段名称:" + field.getName()); System.out.println("字段类型:" + field.getType()); // System.out.println("Modifiers: " + java.lang.reflect.Modifier.toString(field.getModifiers())); System.out.println("---"); } } } // 列表命令 @CommandLine.Command(name = "list", description = "查看文件列表", mixinStandardHelpOptions = true) public class ListCommand implements Runnable { public void run() { // 整个项目的根路径 String projectPath = System.getProperty("user.dir"); // 输入路径 String inputPath = new File(projectPath, "yuzi-generator-demo-projects/acm-template").getAbsolutePath(); List<File> files = FileUtil.loopFiles(inputPath); System.out.println("模板文件列表:"); for (File file : files) { System.out.println(file); } } } // 生成动态文件命令 @CommandLine.Command(name = "generate", description = "生成代码", mixinStandardHelpOptions = true) @Data public class GenerateCommand implements Callable<Integer> { @CommandLine.Option(names = {"-l", "--loop"}, arity = "0..1", description = "是否循环", interactive = true, echo = true) private boolean loop; @CommandLine.Option(names = {"-a", "--author"}, arity = "0..1", description = "作者", interactive = true, echo = true) private String author = "yupi"; @CommandLine.Option(names = {"-o", "--outputText"}, arity = "0..1", description = "输出文本", interactive = true, echo = true) private String outputText = "sum = "; public Integer call() throws Exception { MainTemplateConfig mainTemplateConfig = new MainTemplateConfig(); BeanUtil.copyProperties(this, mainTemplateConfig); System.out.println("配置信息:" + mainTemplateConfig); MainGenerator.doGenerate(mainTemplateConfig); return 0; } } - generate命令:
-
全局调用入口
public class Main { public static void main(String[] args) { CommandExecutor executor = new CommandExecutor(); executor.doExecute(args); } } -
jar包构建
-
使用
maven-assembly-plugin插件将依赖打入jar包。<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest> <mainClass>com.yupi.Main</mainClass> <!-- 替换为你的主类的完整类名 --> </manifest> </archive> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> -
执行
mvn package命令生成jar包。可以看到指定 jar 包已生成
-
-
测试使用
-
使用
java -jar命令运行jar包。 -
示例命令:
java -jar yuzi-generator-basic-1.0-SNAPSHOT-jar-with-dependencies.jar generate -l -o -a liyupi
-
-
封装脚本
-
Linux Bash脚本:
#!/bin/bash java -jar target/yuzi-generator-basic-1.0-SNAPSHOT-jar-with-dependencies.jar "$@" -
Windows批处理文件:
@echo off java -jar target/yuzi-generator-basic-1.0-SNAPSHOT-jar-with-dependencies.jar %* -
使用脚本
liuchuangchuang@liuchuangchuangdeMacBook-Air yuzi-generator % ./generate generate -a -l -o Enter value for --author (作者): lcc22 Enter value for --loop (是否循环): true Enter value for --outputText (输出文本): 嘿嘿 配置信息:MainTemplateConfig(author=lcc22, loop=true, outputText=嘿嘿) acm-template src com liucc acm liuchuangchuang@liuchuangchuangdeMacBook-Air yuzi-generator %可以根据实际 jar 包所在的实际位置,动态调整脚本内容!
-
五、总结
- 命令模式的巧用
- 命令:
Runnable接口中的run()方法 - 具体命令:
ConfigCommand、ListCommand、GenerateCommand - 接收方:比如
MainGenerator的doGenerate()方法 - 调用方:
CommandExecutor具体指向 - 客户端:主程序
Main
- 命令:
- 通过脚本封装对 jar 包的复杂调用