|
|
@@ -0,0 +1,71 @@
|
|
|
+# process-demo
|
|
|
+
|
|
|
+Maven 单模块示例工程(目标 Java 17),用于学习 **`ProcessBuilder` / `Process` / `ProcessHandle`**:如何构建一个子进程、读写它的标准输入输出、重定向流、异步处理 I/O、获取执行状态码,以及组合成管道式调用和进程树管理。
|
|
|
+
|
|
|
+所有说明文档与代码注释均为中文,配套教程见 [`process.md`](process.md)(大纲)、[`doc.md`](doc.md)(完整教程)与 [`blog.md`](blog.md) / [`blog2.md`](blog2.md)(博客文章)。
|
|
|
+
|
|
|
+## 技术栈
|
|
|
+
|
|
|
+- Java 17(`maven.compiler.source/target = 17`,UTF-8 编码)
|
|
|
+- 运行时依赖:`logback-classic`(提供 SLF4J 日志)
|
|
|
+- 测试依赖:`junit-jupiter`(JUnit 5,由 `maven-surefire-plugin` 驱动)
|
|
|
+- 无 lint、无 `exec-maven-plugin`
|
|
|
+
|
|
|
+## 目录结构
|
|
|
+
|
|
|
+```
|
|
|
+src/main/java/space/anyi/process/ 示例代码(public static void main 入口)
|
|
|
+ Jdk.java 共享工具:javac/java 绝对路径、编译 fixture
|
|
|
+ QuickStart.java 示例一:构建并运行子进程
|
|
|
+ ProcessIOExample.java 示例二:标准输入/输出/错误流
|
|
|
+ ProcessRedirectExample.java 重定向:合并流、文件重定向、Redirect 枚举
|
|
|
+ ProcessAsyncExample.java 异步:线程 / CompletableFuture / onExit
|
|
|
+ ExecuteStatusExample.java 示例三:waitFor 阻塞与超时销毁
|
|
|
+ ProcessBuilderExample.java 环境变量(environment())
|
|
|
+ ProcessPipelineExample.java 示例五:管道式组合调用
|
|
|
+ ProcessHandleExample.java 示例六:ProcessHandle 进程管理
|
|
|
+src/main/resources/code/ 子进程程序(fixtures),各目录含 Main.java
|
|
|
+ hello/ 打印问候语与 PID
|
|
|
+ io/ 回显 stdin、统计并写入 stderr
|
|
|
+ executeStatus/ 睡眠 10 秒后退出
|
|
|
+ processHandle/ 打印 PID/参数,睡眠 30 秒等待父进程观察
|
|
|
+src/test/java/space/anyi/process/ ProcessMechanicsTest(端到端测试)
|
|
|
+doc.md / process.md / blog.md / blog2.md 教程与博客文档
|
|
|
+```
|
|
|
+
|
|
|
+## 运行示例
|
|
|
+
|
|
|
+工程没有运行目标,编译后手动运行。需要把依赖 jar 一起放到 classpath(示例使用 slf4j 日志),否则出现 `NoClassDefFoundError: org/slf4j/LoggerFactory`。
|
|
|
+
|
|
|
+```bash
|
|
|
+mvn -q compile
|
|
|
+java -cp "target/classes:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout | tail -1)" space.anyi.process.<ClassName>
|
|
|
+```
|
|
|
+
|
|
|
+例如:
|
|
|
+
|
|
|
+```bash
|
|
|
+java -cp "target/classes:$(mvn -q dependency:build-classpath -Dmdep.outputFile=/dev/stdout | tail -1)" space.anyi.process.QuickStart
|
|
|
+```
|
|
|
+
|
|
|
+> 注意:示例的 `workDir` 相对于 `user.dir`,**必须从仓库根目录运行**,否则 `ProcessBuilder.start()` 抛 `IOException: error=2`。
|
|
|
+
|
|
|
+## 测试
|
|
|
+
|
|
|
+```bash
|
|
|
+mvn test
|
|
|
+```
|
|
|
+
|
|
|
+`ProcessMechanicsTest` 端到端驱动同一批 child fixtures(会先重新编译 `io/` 与 `executeStatus/`),并刻意覆盖「流排水 + 超时/`destroy`」的常见陷阱。同样要求从仓库根目录运行。
|
|
|
+
|
|
|
+## 教程要点(出自 doc.md 的踩坑总结)
|
|
|
+
|
|
|
+1. `new BufferedReader(...).lines().forEach(...)` 会**阻塞当前线程直到子进程退出**;做限时 `waitFor(timeout)` 时必须在后台线程排水。
|
|
|
+2. `Process.waitFor(timeout, unit)` 返回的是 `boolean` 而非退出码;超时返回 `false` 时子进程仍存活,此时调用 `exitValue()` 会抛 `IllegalThreadStateException`,须先 `destroy()` 再阻塞 `waitFor()`。
|
|
|
+3. 生成的子进程 PATH 往往缺少 JDK bin 目录(`error=2`),请统一走 `Jdk.javac()` / `Jdk.java()` 的绝对路径,不要裸用 `javac` / `java`。
|
|
|
+
|
|
|
+## 约定
|
|
|
+
|
|
|
+- 每个案例点由一个独立的 `static` 方法演示,统一由 `main()` 调用。
|
|
|
+- 所有子进程程序均来自 `src/main/resources/code/`,不使用临时脚本或系统命令。
|
|
|
+- 代码注释为中文,说明每步的作用与原因。
|