说明
最近遇到的一个需求:在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();
}
}
}