likes
comments
collection
share

在Node.js中使用JVM运行Java程序

作者站长头像
站长
· 阅读数 34

由于在国内只有2种后端程序员Java8和其他,因此,对于那些选择其他技术栈进行后端开发的工程师来说,掌握Java的使用能力无疑是一项必备技能。

有多种途径可以实现Node.js与Java代码之间的跨语言交互,如将Node.js代码编译为GraalVM支持的中间语言,或利用C++、Rust等语言编写JNI(Java Native Interface)插件。本文将重点介绍通过node-gyp构建自定义Node.js模块的方式来实现这一功能。

JNI作为Java平台标准版(Java SE Platform)的关键组成部分,为Java代码与其他语言编写的代码之间的交互提供了桥梁。借助JNI,你可以在C/C++代码中调用Java方法,反之亦然。

接下来,我们将通过一个示例来展示如何在C++中使用JNI来实例化一个Java类并调用其方法。

1. 首先编写一个简单的 Java 类

package com.example;  
  
public class MyClass {  
    public String getMessage(String message) {  
        return "Hello " + message;  
    }
}

2. 编译 Java 类

javac com/example/MyClass.java

3. 使用node-gyp构建模块

创建一个 node-gyp 模块

mkdir my-node-app
cd my-node-app
npm install node-gyp node-addon-api
node-gyp init

使用 node-gyp init 在当前目录下生成配置文件,包括 binding.gyp

my-node-app/ 
├── binding.gyp 
├── package.json 
└── src/ 
    ├── index.js
    └── my_node_addon.cpp

创建 my_node_addon.cpp 用于初始化 JVM 和调用 JNI

// my_node_addon.cpp
#include <jni.h>  
#include <dlfcn.h> // 用于动态加载 Java 库  
#include "node_addon_api.h"   
  
// JVM 和 JNIEnv 的全局变量  
JavaVM* jvm;  
JNIEnv* jni;  
  
// 初始化 JVM
bool initializeJVM(const char* classPath) {  
    JavaVMInitArgs vm_args;  
    JavaVMOption* options = new JavaVMOption[1];  
    options[0].optionString = const_cast<char*>(classPath);  
    options[0].ignoreUnrecognized = JNI_FALSE;  
    vm_args.version = JNI_VERSION_1_8;  
    vm_args.nOptions = 1;  
    vm_args.options = options;  
    vm_args.ignoreUnrecognized = false;  
  
    if (JNI_CreateJavaVM(&jvm, (void**)&jni, &vm_args) != JNI_OK) {  
        return false;  
    }  
    return true;  
}
  
// 清理 JVM  
void cleanupJVM() {  
    if (jvm != nullptr) {  
        jvm->DestroyJavaVM();  
        jvm = nullptr;  
        jni = nullptr;  
    }  
}

Napi::Value CallJava(const Napi::CallbackInfo& info) {  
    Napi::Env env = info.Env();
    
    if (info.Length() < 3) {  
        Napi::TypeError::New(env, "Expected class name, method name, and argument as arguments").ThrowAsJavaScriptException();  
        return env.Null();  
    } 
  
    std::string className = info[0].As<Napi::String>().Utf8Value();  
    std::string methodName = info[1].As<Napi::String>().Utf8Value();
    std::string arg = info[2].As<Napi::String>().Utf8Value();
  
    // 查找 Java 类  
    jclass cls = jni->FindClass(className.c_str());  
    if (cls == nullptr) {  
        env.ThrowTypeError("Java class not found");  
        return env.Null();  
    }  
  
    // 获取类的构造方法  
    jmethodID ctor = jni->GetMethodID(cls, "<init>", "()V");  
    if (ctor == nullptr) {  
        env.ThrowTypeError("Constructor not found");  
        return env.Null();  
    }  
  
    // 创建 Java 类的实例  
    jobject obj = jni->NewObject(cls, ctor);  
    if (jni->ExceptionOccurred()) {  
        jni->ExceptionDescribe();  
        jni->ExceptionClear();  
        env.ThrowTypeError("Failed to create Java object");  
        return env.Null();  
    }  
  
    // 查找实例方法  
    jmethodID mid = jni->GetMethodID(cls, methodName.c_str(), "()Ljava/lang/String;");  
    if (mid == nullptr) {  
        env.ThrowTypeError("Java method not found");  
        return env.Null();  
    }
    
    jstring argStr = jni->NewStringUTF(arg.c_str()); 
  
    // 调用实例方法  
    jstring result = (jstring)jni->CallObjectMethod(obj, mid, argStr);  
    if (jni->ExceptionOccurred()) {  
        jni->ExceptionDescribe();  
        jni->ExceptionClear();  
        env.ThrowTypeError("Java exception occurred during method call");  
        return env.Null();  
    }  
  
    // 将 Java 字符串转换为 C++ 字符串并返回给 Node.js  
    const char* resultChars = jni->GetStringUTFChars(result, nullptr);  
    std::string resultStr(resultChars);  
    jni->ReleaseStringUTFChars(result, resultChars);  
  
    // 清理本地引用  
    jni->DeleteLocalRef(obj);  
    jni->DeleteLocalRef(result);  
  
    return Napi::String::New(env, resultStr);  
}  
  
Napi::Object Init(Napi::Env env, Napi::Object exports) {  
    const char* classPath = "-Djava.class.path=/path/to/your/java/classes"; // Java 类路径  
    // 初始化 JVM
    if (!initializeJVM(classPath)) {  
        throw Napi::Error::New(env, "Failed to initialize JVM");  
    } 
  
    // 绑定函数到 exports  
    exports.Set(Napi::String::New(env, "callJava"), Napi::Function::New(env, CallJava));  
  
    // 注册清理 JVM 的钩子  
    Napi::AddCleanupHook([](void* arg) {   
        cleanupJVM();  
    }, nullptr);  
  
    return exports;  
}
  
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
// binding.gyp
{  
  "targets": [  
    {  
      "target_name": "my_node_addon",  
      "sources": [ "my_node_addon.cpp"],  
      "include_dirs": ["<!(node -p \"require('node-addon-api').include\")"],  
      "dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],  
      "cflags!": [ "-fno-exceptions" ],  
      "cflags_cc!": [ "-fno-exceptions" ],    
      "conditions": [         
        ['OS=="linux"', {  
          "cflags+": ["-I/usr/lib/jvm/java-8-openjdk-amd64/include", "-I/usr/lib/jvm/java-8-openjdk-amd64/include/linux"],  
          "libraries": ["-L/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server", "-ljvm"]  
        }]  
      ]  
    }  
  ]  
}

4. 构建插件

node-gyp configure build

5. 在 Node.js 中使用

const addon = require('my-node-addon');

addon.callJava('com/example/MyClass', 'getMessage', 'Node.js'); // Hello Node.js

使用 node-java 库简化工作流程

使用 node-java 库可以简化 Node.js 调用 Java 代码的过程,因为它提供了一个高级别的接口来加载 Java 类、创建 Java 对象以及调用 Java 方法。使得我们无需直接处理 JNI(Java Native Interface)的复杂性, 下面我们使用node-java完成同样的效果。

首先需要安装node-java包:

npm install java

修改代码使用node-java

const java = require('java');  

// 设置 classpath
java.classpath.push("/path/to/your/java/classes");

// 加载 Java 类  
const MyClass = java.import('com.example.MyClass');  

const myClass = new MyClass();
// getMessageSync 是 node-java 动态代理的 getMessage 方法的同步版本
const result = myClass.getMessageSync('Node.js');  

console.log(result); // Hello Node.js

在TypeScript中使用

假设你有一个 Web 项目,目录结构如下:

web-framework/
└── java/
│   ├── lib/                 
│   │   ├── fastjson-1.2.83.jar
│   │   └── ...
│   ├── bin/                 
│   │   ├── MyClass.class
│   │   └── ...
├── src/                         
│   ├── index.ts                 
│   ├── service/                 
│   │   ├── JvmService.ts
│   │   └── ...
└── ...                          

将 node-java 封装成一个服务。

// JvmService.ts
import { Injectable } from '@nestjs/common';
import fg from 'fast-glob';
import java from 'java';
import path from 'path';

@Injectable()
export class JvmService {
  private readonly jvm: any;
  constructor() {
    // 自动扫描所有的 .jar 文件并加入到 classpath
    fg.globSync('java/lib/*.jar', {
      cwd: process.cwd(),
      absolute: true,
      objectMode: true,
      unique: true,
    }).forEach((file) => {
      console.log('加载jar包:', file.name);
      java.classpath.push(file.path);
    });
    // 将自己编写的 Java 文件加入 classpath
    java.classpath.push(path.join(process.cwd(), 'java/bin'));
    this.jvm = java;
  }

  get JVM() {
    return this.jvm;
  }

  import(path: string) {
    return this.jvm.import(path);
  }
}

使用 JvmService

@Injectable()
export class TestService {
  constructor(
    private readonly jvmService: JvmService,
  ) {
    const myClass = this.jvmService.import('com/example/MyClass');
    const result = myClass.getMessageSync('Node.js');  

    console.log(result); // Hello Node.js
  }
转载自:https://juejin.cn/post/7361976584842706963
评论
请登录