记一次Java与子进程交互的案例

说明

最近遇到的一个需求:在A应用中集成B公司的应用。而B应用的所有功能依赖于B安装路径下面的动态链接库,从Path环境变量中读取。

问题是:A启动时就会读取Path,并且之后不会再读。如果此时Path中不包含B的动态链接库,则无法使用B的相关功能(并且不能通过 -Djava.library.path指定,因为B依赖多个动态链接库,通过一个System.loadLibrary()加载的)。

当然,可以让用户将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<Map<String, Consumer<Integer>>> idToAddResMap = new AtomicReference<>(new HashMap<>());
    private final Map<String, Consumer<Integer>> idToAddResMap = new HashMap<>();
    private final Map<String, Consumer<Integer>> idToDivResMap = new HashMap<>();

    public BProcessCaller(Map<String, String> 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<Integer> 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<Integer> 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<String, String> 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<Integer> futureAdd = new CompletableFuture<>();
            System.out.print("get val by (10 + 20):\\t");
            caller.add(10, 20, futureAdd::complete);
            System.out.println(futureAdd.get());

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

            CompletableFuture<Integer> 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();
        }
    }

}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

非理性地界生物

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值