Java 子进程案例

说明

最近遇到的一个需求:在A应用中集成B公司的应用。而B应用的所有功能依赖于B安装路径下面的动态链接库,从Path环境变量中读取。
问题是:A启动时就会读取Path,并且之后不会再读。如果此时Path中不包含B的动态链接库,则无法使用B的相关功能。
当然,可以让用户将B的动态链接库写入Path后,再重启A应用。但是这样会中断用户的操作,让用户体验感下降。
因此打算使用在子进程中处理B的相关功能的折中方案。
简单的说,就是在 A 进程中启动子进程 B 来完成某些操作,并获取返回结果。

实现

这里用 加法 和 除法作为案例。

A 启动子进程 B,在B中完成加法除法操作,并获取结果。

其通信使用 json 格式。约定参数为:

public class SignalProperties {

    private SignalProperties() {
    }

    public static final String ID = "id";

    public static final String ACTION = "action";

    // region 加法
    public static final String ACTION_ADD = "action_add";

    public static final String NUM_ADD = "num_add";
    public static final String NUM_AUG = "num_aug";

    public static final String RES_ADD = "res_add";
    // endregion

    // region 除法
    public static final String ACTION_DIVISION = "action_division";

    public static final String NUM_DIVISOR = "num_Divisor";
    public static final String NUM_DIVISOR1 = "num_Divisor1";

    public static final String RES_DIVISION = "res_division";
    // endregion

}

B进程处理逻辑:

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

public class BProcessServer {

    public static void main(String[] args) {
        DataOutputStream out = new DataOutputStream(System.out);
        try (DataInputStream reader = new DataInputStream(System.in)) {
            String line;
            while (!StrUtil.isEmpty((line = reader.readUTF()))) {
                if ("exit".equalsIgnoreCase(line)) {
                    out.writeUTF("");
                    return;
                }

                if (JSONUtil.isJson(line)) {
                    JSONObject json = JSON.parseObject(line);
                    JSONObject resJson = new JSONObject();
                    String id = json.getString(SignalProperties.ID);
                    switch (json.getString(SignalProperties.ACTION)) {
                        case SignalProperties.ACTION_ADD:
                            int add = json.getInteger(SignalProperties.NUM_ADD);
                            int aug = json.getInteger(SignalProperties.NUM_AUG);
                            int res = add + aug;
                            resJson.put(SignalProperties.ACTION, SignalProperties.ACTION_ADD);
                            resJson.put(SignalProperties.RES_ADD, res);
                            resJson.put(SignalProperties.ID, id);
                            out.writeUTF(resJson.toJSONString());
                            break;
                        case SignalProperties.ACTION_DIVISION:
                            int divisor = json.getInteger(SignalProperties.NUM_DIVISOR);
                            int divisor1 = json.getInteger(SignalProperties.NUM_DIVISOR1);
                            int resDiv = divisor / divisor1;
                            resJson.put(SignalProperties.ACTION, SignalProperties.ACTION_DIVISION);
                            resJson.put(SignalProperties.RES_DIVISION, resDiv);
                            resJson.put(SignalProperties.ID, id);
                            out.writeUTF(resJson.toJSONString());
                            break;
                        case "otherAction":
                            break;
                        default:
                            System.out.println(line);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception ex) {
            ex.printStackTrace();
            try {
                out.writeUTF("");
            } catch (IOException exception) {
                exception.printStackTrace();
            }
        }
    }
}

A 进程调用B进程的逻辑:

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.function.Consumer;

public class BProcessCaller {

    private final Process process;
    private final DataOutputStream dos;

    // private final AtomicReference>> idToAddResMap = new AtomicReference<>(new HashMap<>());
    private final Map> idToAddResMap = new HashMap<>();
    private final Map> idToDivResMap = new HashMap<>();

    public BProcessCaller(Map env) throws IOException {
        ProcessBuilder processBuilder = new ProcessBuilder(resolveCommand(getCommand()));
        if (MapUtil.isNotEmpty(env)) {
            processBuilder.environment().putAll(env);
        }
        process = processBuilder.start();

        // region 监听子进程的错误输出
        new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.err.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        // endregion

        // region 监听子进程的标准输出
        new Thread(() -> {
            try (DataInputStream dis = new DataInputStream(process.getInputStream())) {
                String line;
                while (!StrUtil.isEmpty((line = dis.readUTF()))) {
                    // System.out.println(line);
                    if (JSONUtil.isJson(line)) {
                        JSONObject json = JSON.parseObject(line);
                        String id = json.getString(SignalProperties.ID);
                        String action = json.getString(SignalProperties.ACTION);
                        switch (action) {
                            case SignalProperties.ACTION_ADD:
                                Integer resAdd = json.getInteger(SignalProperties.RES_ADD);
                                this.idToAddResMap.get(id).accept(resAdd);
                                break;
                            case SignalProperties.ACTION_DIVISION:
                                Integer resDiv = json.getInteger(SignalProperties.RES_DIVISION);
                                this.idToDivResMap.get(id).accept(resDiv);
                                break;
                            case "otherAction":
                                /* 处理结果 */
                                break;
                            default:
                                System.out.println(action);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
        // endregion

        dos = new DataOutputStream(process.getOutputStream());
    }

    private String getCommand() {
        String javaHome = System.getProperty("java.home");
        String java = javaHome + File.separator + "bin" + File.separator + "java";
        String sysCp = System.getProperty("java.class.path");
        String currPath = ClassLoader.getSystemResource("").getPath();
        String cp = "\"" + sysCp + File.pathSeparator + currPath + "\"";

        String charset = " -Dfile.encoding=" + Charset.defaultCharset();
        return java + charset + " -cp " + cp + BProcessServer.class;
    }

    private void sendToChild(String message) {
        try {
            dos.writeUTF(message);
            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // region 调用 B 的接口

    public void add(int add, int aug, Consumer consumer) {
        String id = UUID.randomUUID().toString();
        JSONObject json = new JSONObject();
        json.put(SignalProperties.ID, id);
        json.put(SignalProperties.ACTION, SignalProperties.ACTION_ADD);
        json.put(SignalProperties.NUM_ADD, add);
        json.put(SignalProperties.NUM_AUG, aug);

        this.idToAddResMap.put(id, consumer);
        sendToChild(json.toJSONString());

    }

    public void div(int divisor, int divisor1, Consumer consumer) {
        String id = UUID.randomUUID().toString();
        JSONObject json = new JSONObject();
        json.put(SignalProperties.ID, id);
        json.put(SignalProperties.ACTION, SignalProperties.ACTION_DIVISION);
        json.put(SignalProperties.NUM_DIVISOR, divisor);
        json.put(SignalProperties.NUM_DIVISOR1, divisor1);

        this.idToDivResMap.put(id, consumer);
        sendToChild(json.toJSONString());
    }

    public void exit() {
        sendToChild("exit");
    }

    // endregion

    public boolean isChildAlive() {
        return process.isAlive();
    }

    public void destroyChild() {
        process.destroy();
    }

    private String[] resolveCommand(String command) {
        if (command.length() == 0) {
            throw new IllegalArgumentException("Empty command");
        }

        StringTokenizer st = new StringTokenizer(command);
        String[] cmdArray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++) {
            cmdArray[i] = st.nextToken();
        }
        return cmdArray;
    }

    // region Singleton
    private static volatile BProcessCaller INSTANCE;

    public static BProcessCaller getInstance(Map env) throws IOException {
        if (INSTANCE == null) {
            synchronized (BProcessCaller.class) {
                if (INSTANCE == null) {
                    INSTANCE = new BProcessCaller(env);
                }
            }
        }
        return INSTANCE;
    }

    public static BProcessCaller getInstance() {
        return INSTANCE;
    }
    // endregion

}

A 进程Client:

import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class AClient {

    public static void main(String[] args) {
        try {
            BProcessCaller caller = BProcessCaller.getInstance(new HashMap<>());

            CompletableFuture futureAdd = new CompletableFuture<>();
            System.out.print("get val by (10 + 20):\t");
            caller.add(10, 20, futureAdd::complete);
            System.out.println(futureAdd.get());

            CompletableFuture futureDiv = new CompletableFuture<>();
            System.out.print("get val by (20 / 10):\t");
            caller.div(20, 10, futureDiv::complete);
            System.out.println(futureDiv.get());

            CompletableFuture futureErr = new CompletableFuture<>();
            System.out.print("get val by (20 / 0):\t");
            caller.div(20, 0, futureErr::complete);
            System.out.println(futureErr.get(1, TimeUnit.SECONDS));

            caller.exit();
        } catch (IOException | InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }
    }

}

你可能感兴趣的:(Java 子进程案例)