فهرست منبع

示例一:通过 ProcessBuilder 构建并启动子进程

yangyi 1 هفته پیش
والد
کامیت
9a632019b9
2فایلهای تغییر یافته به همراه96 افزوده شده و 16 حذف شده
  1. 66 0
      src/main/java/space/anyi/process/Jdk.java
  2. 30 16
      src/main/java/space/anyi/process/QuickStart.java

+ 66 - 0
src/main/java/space/anyi/process/Jdk.java

@@ -0,0 +1,66 @@
+package space.anyi.process;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 各示例共用的 JDK 工具类:
+ * <ul>
+ *   <li>定位 javac / java 的绝对路径——子进程的 PATH 中往往没有 JDK 的 bin 目录,不能写裸 javac/java;</li>
+ *   <li>提供编译子进程程序的统一方法,供每个示例复用。</li>
+ * </ul>
+ */
+final class Jdk {
+    private static final Logger log = LoggerFactory.getLogger(Jdk.class);
+
+    private Jdk() {
+    }
+
+    /** 返回 JDK 安装目录,优先取环境变量 JAVA_HOME,否则取当前 JVM 所在目录 */
+    private static String home() {
+        return System.getenv("JAVA_HOME") != null
+                ? System.getenv("JAVA_HOME")
+                : System.getProperty("java.home");
+    }
+
+    /** javac 可执行文件的绝对路径(Windows 下扩展名为 .exe) */
+    static String javac() {
+        return Paths.get(home(), "bin", isWindows() ? "javac.exe" : "javac").toString();
+    }
+
+    /** java 可执行文件的绝对路径(Windows 下扩展名为 .exe) */
+    static String java() {
+        return Paths.get(home(), "bin", isWindows() ? "java.exe" : "java").toString();
+    }
+
+    /** 编译工作目录下的 Main.java;编译失败(退出码非 0)时抛出异常 */
+    static void compileFixture(File workDir) throws IOException, InterruptedException {
+        Process process = new ProcessBuilder(javac(), "-encoding", "UTF-8", "Main.java")
+                .directory(workDir)
+                .redirectErrorStream(true)
+                .start();
+        // 异步消费 javac 的输出,避免输出量超过管道缓冲区时 javac 写阻塞、无法退出
+        Thread drain = new Thread(
+                () -> new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))
+                        .lines()
+                        .forEach(line -> log.info("[javac] {}", line)),
+                "javac-drain");
+        drain.setDaemon(true);
+        drain.start();
+        int exit = process.waitFor();
+        if (exit != 0) {
+            throw new IllegalStateException("fixture 编译失败, exit=" + exit);
+        }
+    }
+
+    private static boolean isWindows() {
+        return System.getProperty("os.name").toLowerCase().contains("win");
+    }
+}

+ 30 - 16
src/main/java/space/anyi/process/QuickStart.java

@@ -1,28 +1,42 @@
 package space.anyi.process;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.util.List;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+/**
+ * 示例一:通过 ProcessBuilder 构建并启动子进程。
+ *
+ * 用 ProcessBuilder 指定要运行的命令,调用 start() 启动子进程;
+ * 子进程为 src/main/resources/code/hello/Main.java(向标准输出打印两行后正常退出)。
+ */
 public class QuickStart {
-    private final static Logger log = LoggerFactory.getLogger(QuickStart.class);
+    private static final Logger log = LoggerFactory.getLogger(QuickStart.class);
+
     public static void main(String[] args) throws IOException, InterruptedException {
-        //1.ProcessBuilder构建Process
-        List<String> cmd = List.of("java", "--version");
-        Process process = new ProcessBuilder(cmd)
-                //启动Process
+        // 子进程程序所在目录(相对当前工程根目录),先编译再运行
+        File workDir = Paths.get(System.getProperty("user.dir"), "src", "main", "resources", "code", "hello").toFile();
+        Jdk.compileFixture(workDir);
+
+        // 1. 通过 ProcessBuilder 构建命令并启动子进程:
+        //    命令要素 = 可执行文件绝对路径 + Main 类名;directory() 指定子进程的工作目录
+        Process process = new ProcessBuilder(Jdk.java(), "-Dfile.encoding=UTF-8", "Main")
+                .directory(workDir)
                 .start();
-        //2.获取Process标准输出到程序中
-        new BufferedReader(new InputStreamReader(process.getInputStream()))
+
+        // 2. 读取子进程的标准输出(println 输出的内容)
+        new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))
                 .lines()
-                .forEach((line)->{log.info("标准输出:{}",line);});
-        //3.获取Process执行结果的状态码,0:表示正常退出;1:表示异常退出
+                .forEach(line -> log.info("子进程标准输出: {}", line));
+
+        // 3. 等待子进程结束,返回退出状态码,0 表示正常退出
         int exitStatus = process.waitFor();
-//        int exitValue = process.exitValue();
-        log.info("退出状态码:{}",exitStatus);
+        log.info("子进程退出状态码: {}", exitStatus);
     }
-}
+}