将Java类封装成JSON RPC 2.0服务,通过标准输入输出流发布。在nodejs中启动服务子进程,读写标准输入输出流,实现接口调用。

1. 方案

将现有Java库封装为JSON-RPC 2.0服务。假设现有Javalib.jar库,提供Javalib类和方法 String hello(String name) 。编写封装类,将Java方法封装成JSON-RPC 2.0接口。

代码1  将Java方法封装成JSON-RPC 2.0接口

private JSONObject serve(JSONObject request) {
  String method = request.getString("method");
  int id = request.getInt("id");
  JSONObject params = request.getJSONObject("params");

  JSONObject response = new JSONObject();
  response.put("jsonrpc", "2.0");
  response.put("id", id);

  JSONObject error = new JSONObject();

  switch (String.valueOf(method)) {
  case "hello": {
    String name = params.getString("name");
    Javalib javalib = new Javalib();
    String result = javalib.hello(name);
    response.put("result", result);
    break;
  }
  case "exit": 
    response.put("result", "exit");
    running = false;
    break;
  default:
    error.put("code", -32601);
    error.put("messsage", "method not found");
    response.put("error", error);
    break;
  }

  return response;
}

编写命令行程序,通过标准输入输出发布JSON-RPC 2.0服务。

代码2  通过标准输入输出发布JSON-RPC 2.0服务

private String serve(String jsonStr) {
  JSONObject request = new JSONObject(jsonStr);
  JSONObject response = serve(request);
  return response.toString();
}

public static void main(String[] args) throws IOException {
  JsonRpcServer server = new JsonRpcServer();
  Scanner scanner = new Scanner(System.in);

  while (server.running) {
    String jsonStr = scanner.nextLine();
    System.out.println(server.serve(jsonStr));
  }
}

在nodejs中启动服务程序,通过标准输入输出实现远程调用

代码3  在nodejs中通过标准输入输出实现远程调用

import { spawn } from 'child_process';

class JsonRpcClient {
  constructor() {
    this.counter = 0;
    this.listeners = new Map();
    this.javaProcess = spawn('java', ['-cp', 'Javalib.jar;json-20250517.jar;.', 'JsonRpcServer']);

    this.javaProcess.stdout.on('data', (data) => {
      const jsonStr = data.toString();

      try {
        const { id, result } = JSON.parse(jsonStr);
        if (this.listeners.has(id)) {
          this.listeners.get(id)(jsonStr);
          this.listeners.delete(id);
        }
      } catch (err) {
        console.error('Failed to parse response:', err);
      }
    });

    this.javaProcess.stderr.on('data', (data) => {
      console.error('Java process error:', data.toString());
    });
  }

  async call(method, params = {}) {
    const id = ++this.counter;
    const request = {
      jsonrpc: "2.0",
      id,
      method,
      params,
    };
    const jsonStr = JSON.stringify(request);
    console.log(`发送 ${jsonStr}`);
    this.javaProcess.stdin.write(`${jsonStr}\n`);

    const { promise, resolve } = Promise.withResolvers();
    this.listeners.set(id, resolve);
    return promise;
  }
}

上述步骤完成后,就可以在nodejs中通过JSON-RPC 2.0调用Java方法了。

代码4  通过JSON-RPC 2.0调用Java方法

(async () => {
  const client = new JsonRpcClient();
  try {
    let response = await client.call('hello', { name: 'Bob' })
    console.log(`接收 ${response}`);

    response = await client.call('non_exist');
    console.log(`接收 ${response}`);

    response = await client.call('exit');
    console.log(`接收 ${response}`);

  } catch (err) {
    console.error('RPC call failed:', err);
  }
})();

2. 适用场景、局限和扩展

用于对性能要求不高的nodejs程序(如cli),快速对接现有Java库中的方法。如果对性能要求较高,或调用的Java方法需要支持复杂对象,可以使用node-java实现nodejs和Java的互操作。

由于使用了标准输入输出,上述方案对请求和应答对象的数据类型和大小存在一定限制。如果需要传输少量二进制数据,可以使用十六进制编码或base64编码。如果要传输大量数据,可以首先生成临时文件,将临时文件名作为参数传递。上述方案也可以扩展到“发布-订阅”模式。

3. 示例代码

代码5  Javalib.java

public class Javalib {
  public String hello(String name) {
    return "Hello " + name + " from Java!";
  }
}

代码6  JsonRpcServer.java

// 需要下载org.json包。https://repo1.maven.org/maven2/org/json/json/20250517/json-20250517.jar
import java.io.*;
import java.util.Scanner;
import java.util.Objects;
import org.json.JSONObject;
import org.json.JSONArray;

public class JsonRpcServer {
  public boolean running = true;

  private JSONObject serve(JSONObject request) {
    String method = request.getString("method");
    int id = request.getInt("id");
    JSONObject params = request.getJSONObject("params");

    JSONObject response = new JSONObject();
    response.put("jsonrpc", "2.0");
    response.put("id", id);

    JSONObject error = new JSONObject();

    switch (String.valueOf(method)) {
    case "hello": {
      String name = params.getString("name");
      Javalib javalib = new Javalib();
      String result = javalib.hello(name);
      response.put("result", result);
      break;
    }
    case "exit": 
      response.put("result", "exit");
      running = false;
      break;
    default:
      error.put("code", -32601);
      error.put("messsage", "method not found");
      response.put("error", error);
      break;
    }

    return response;
  }

  private String serve(String jsonStr) {
    JSONObject request = new JSONObject(jsonStr);
    JSONObject response = serve(request);
    return response.toString();
  }

  public static void main(String[] args) throws IOException {
    JsonRpcServer server = new JsonRpcServer();
    Scanner scanner = new Scanner(System.in);

    while (server.running) {
      String jsonStr = scanner.nextLine();
      System.out.println(server.serve(jsonStr));
    }
  }
}

代码7  app.js

import { spawn } from 'child_process';

class JsonRpcClient {
  constructor() {
    this.counter = 0;
    this.listeners = new Map();
    this.javaProcess = spawn('java', ['-cp', 'Javalib.jar;json-20250517.jar;.', 'JsonRpcServer']);

    this.javaProcess.stdout.on('data', (data) => {
      const jsonStr = data.toString();

      try {
        const { id, result } = JSON.parse(jsonStr);
        if (this.listeners.has(id)) {
          this.listeners.get(id)(jsonStr);
          this.listeners.delete(id);
        }
      } catch (err) {
        console.error('Failed to parse response:', err);
      }
    });

    this.javaProcess.stderr.on('data', (data) => {
      console.error('Java process error:', data.toString());
    });
  }

  async call(method, params = {}) {
    const id = ++this.counter;
    const request = {
      jsonrpc: "2.0",
      id,
      method,
      params,
    };
    const jsonStr = JSON.stringify(request);
    console.log(`发送 ${jsonStr}`);
    this.javaProcess.stdin.write(`${jsonStr}\n`);

    const { promise, resolve } = Promise.withResolvers();
    this.listeners.set(id, resolve);
    return promise;
  }
}

(async () => {
  const client = new JsonRpcClient();
  try {
    let response = await client.call('hello', { name: 'Bob' })
    console.log(`接收 ${response}`);

    response = await client.call('non_exist');
    console.log(`接收 ${response}`);

    response = await client.call('exit');
    console.log(`接收 ${response}`);

  } catch (err) {
    console.error('RPC call failed:', err);
  }
})();

代码8  测试输入input.text

{"jsonrpc": "2.0", "id": 1, "method": "hello", "params": {"name": "Alex"}}
{"jsonrpc": "2.0", "id": 1, "method": "exit", "params": {}}

代码9  测试脚本

# 编译Javalib
javac Javalib.java && jar cvf Javalib.jar Javalib.class

# 编译JsonRpcServer
javac -cp "Javalib.jar;json-20250517.jar" JsonRpcServer.java

# 测试JsonRpcServer
gc input.txt | java -cp "Javalib.jar;json-20250517.jar;." JsonRpcServer 

# 测试app.js
node app.js

4. 附录:JSON-RPC 2.0简介

JSON-RPC 2.0是一种轻量级、无状态的远程过程调用(RPC)协议,使用JSON作为数据交换格式。

表1  请求对象
字段名 类型 必填 说明
jsonrpc string 固定为 "2.0"
method string 方法名
params array/object 参数
id string/number 请求标识符

代码10  请求对象示例

{
  "jsonrpc": "2.0",
  "method": "subtract",
  "params": [42, 23],
  "id": 1
}
表2  响应对象
字段名 类型 必填 说明
jsonrpc string 固定为 "2.0"
result any 结果
error 错误对象 错误信息
id string/number 与请求标识符一致
表3  错误对象
字段名 类型 必填 说明
code number 错误码
message string 错误简介
data any 详细信息
表4  预定义错误码
错误码 说明
-32700 Parse error 语法解析错误
-32600 Invalid Request 无效请求
-32601 Method not found 找不到方法
-32602 Invalid params 无效的参数
-32603 Internal error 内部错误
-32000 到 -32099 Server error 服务端错误
表5  编程语言支持
语言 库/框架
JavaScript json-rpc-protocol
Python jsonrpcserver
Go jsonrpc2
Java jsonrpc4j

5. 参考资料

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐