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

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335