杰瑞科技汇

JS如何高效调用Java方法?

  1. 前端浏览器环境中的 JavaScript 调用后端 Java 代码
  2. JavaScript 引擎(如 Node.js)中直接调用 Java 代码

前端浏览器环境中的 JavaScript 调用后端 Java 代码

这是最常见、最标准的场景,浏览器中的 JavaScript 运行在客户端,而 Java 代码运行在服务器端,它们之间不能直接调用,必须通过网络通信来完成。

JS如何高效调用Java方法?-图1
(图片来源网络,侵删)

核心思想:客户端-服务器架构

前端 JS 作为客户端,向后端 Java 应用发送一个请求(通常是 HTTP 请求),后端 Java 应用接收到请求后执行相应的业务逻辑,然后将结果以某种格式(如 JSON)返回给前端。

主要实现方式

RESTful API (最主流、最推荐的方式)

这是目前业界最标准、最通用的做法,后端 Java 应用暴露一组 URL 接口(端点),前端通过 HTTP 方法(GET, POST, PUT, DELETE 等)来调用这些接口。

工作流程:

  1. 后端 (Java):

    JS如何高效调用Java方法?-图2
    (图片来源网络,侵删)
    • 使用 Java Web 框架(如 Spring Boot)创建一个 Web 服务器。
    • 定义 Controller 类,使用 @RestController@Controller 注解。
    • 在方法上使用 @RequestMapping@GetMapping@PostMapping 等注解来映射 URL。
    • 方法内部执行 Java 代码(如数据库操作、业务逻辑计算),并返回数据,框架会自动将返回的对象序列化为 JSON 格式。
  2. 前端 (JavaScript):

    • 使用 fetch API 或者 axios 等库向后端定义的 URL 发送 HTTP 请求。
    • 接收并处理后端返回的 JSON 数据。

代码示例:

后端 (Spring Boot):

import org.springframework.web.bind.annotation.*;
// @RestController = @Controller + @ResponseBody
@RestController
@RequestMapping("/api/users") // 定义基础路径
public class UserController {
    // 模拟一个用户数据库
    private final Map<Long, String> userDatabase = new HashMap<>();
    {
        userDatabase.put(1L, "Alice");
        userDatabase.put(2L, "Bob");
    }
    // GET 请求: http://localhost:8080/api/users/1
    @GetMapping("/{id}")
    public String getUserById(@PathVariable("id") Long id) {
        System.out.println("Java 后端接收到请求,查找用户 ID: " + id);
        String userName = userDatabase.get(id);
        if (userName == null) {
            // 返回 404 Not Found 状态码和错误信息
            throw new UserNotFoundException("User not found with id: " + id);
        }
        return userName; // Spring Boot 会自动将 String 序列化为 JSON
    }
    // POST 请求: http://localhost:8080/api/users
    @PostMapping
    public String createUser(@RequestBody UserRequest request) {
        System.out.println("Java 后端接收到请求,创建用户: " + request.getName());
        // ... 执行创建用户的逻辑 ...
        return "User created successfully: " + request.getName();
    }
}
// 自定义异常
class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}
// 用于接收 POST 请求体的 DTO
class UserRequest {
    private String name;
    // getter, setter, constructor...
}

前端 (JavaScript - 使用 fetch API):

JS如何高效调用Java方法?-图3
(图片来源网络,侵删)
// 1. 获取用户信息 (GET 请求)
async function fetchUser() {
  const userId = 1;
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      // 如果后端返回 4xx 或 5xx 状态码
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const userName = await response.text(); // 或者 response.json() 如果返回的是 JSON 对象
    console.log('获取到的用户名:', userName);
    // ... 在页面上显示 userName ...
  } catch (error) {
    console.error('获取用户失败:', error);
  }
}
// 2. 创建新用户 (POST 请求)
async function createUser() {
  const newUser = { name: "Charlie" };
  try {
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(newUser),
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const result = await response.text();
    console.log('创建用户结果:', result);
  } catch (error) {
    console.error('创建用户失败:', error);
  }
}
// 调用函数
fetchUser();
createUser();

优点:

  • 解耦: 前后端完全分离,可以独立开发、测试和部署。
  • 标准化: 基于 HTTP 协议,通用性强,任何客户端(Web、移动App)都可以调用。
  • 可扩展性好: 易于负载均衡和水平扩展。

WebSocket (实时双向通信)

如果你的应用需要实时数据推送,比如聊天室、在线游戏、实时股票行情等,WebSocket 比 RESTful API 更合适。

工作流程:

  1. 前端 JS 和后端 Java 建立一个持久化的 WebSocket 连接。
  2. 连接建立后,任何一方都可以主动向另一方发送消息。
  3. 后端 Java 可以主动向前端 JS 推送数据,无需前端轮询请求。

后端 (Java - 使用 Spring WebSocket): 需要配置 WebSocketConfigurer@MessageMapping

前端 (JavaScript): 使用 new WebSocket(url) API。

优点:

  • 实时性: 真正的双向、实时通信。
  • 高效: 避免了 HTTP 的握手开销,适合频繁的小数据量通信。

JavaScript 引擎(如 Node.js)中直接调用 Java 代码

这种场景下,JavaScript 代码运行在 Node.js 环境中(服务器端),目标是直接调用本地的 Java 类库(.jar 文件或 Java 类),而不涉及网络请求。

核心思想:跨语言互操作

Node.js 和 Java 都运行在 Java 虚拟机 上,Node.js 基于 V8 引擎,但可以通过一些工具(如 GraalVM)使其在 JVM 上运行,从而实现与 Java 代码的直接交互。

主要实现方式

使用 GraalVM (现代、高性能的方式)

GraalVM 是一个高性能的多语言虚拟机,它允许你在同一个运行时中混合使用多种语言,包括 JavaScript 和 Java。

工作流程:

  1. 安装 GraalVM: 安装 GraalVM 并设置 JAVA_HOME
  2. 安装 Node.js: 在 GraalVM 的 bin 目录下安装 Node.js (gu install nodejs)。
  3. 编写 Java 代码: 编写你想要被调用的 Java 类。
  4. 编写 JavaScript 代码: 在 Node.js 环境中,通过 java 全局对象来导入和使用 Java 类。

代码示例:

Java 代码 (MyCalculator.java):

package com.example;
public class MyCalculator {
    public int add(int a, int b) {
        System.out.println("Java 方法 add() 被调用了!");
        return a + b;
    }
}

JavaScript 代码 (调用 Java):

// 假设 MyCalculator.class 文件在当前目录的 com/example/ 文件夹下
// 1. 导入 Java 类
const MyCalculator = java.import('com.example.MyCalculator');
// 2. 创建 Java 对象实例
const calculator = new MyCalculator();
// 3. 调用 Java 对象的方法
const result = calculator.add(10, 25);
console.log(`从 Java 返回的结果是: ${result}`);
// 输出:
// Java 方法 add() 被调用了!
// 从 Java 返回的结果是: 35

优点:

  • 高性能: 代码运行在同一个 JVM 中,没有进程间通信的开销。
  • 原生交互: 可以像使用普通 JavaScript 对象一样使用 Java 对象和其方法。
  • 强大的生态系统: 可以无缝使用 Java 庞大的生态系统(如 Spring, Hibernate, Apache Commons 等)。

使用 child_process 模块 (传统方式)

如果你不想使用 GraalVM,或者只是想简单地启动一个 Java 进程并与之交互,可以使用 Node.js 的 child_process 模块。

工作流程:

  1. Node.js: 通过 spawnexec 方法启动一个新的 Java 进程,并传递参数。
  2. Java: 编写一个可以接收命令行参数或标准输入的 Java 程序。
  3. 通信:
    • 标准输入/输出: Node.js 向 Java 进程的 stdin 写入数据,从 stdout 读取结果。
    • 退出码: Java 进程执行完毕后,通过退出码表示成功或失败。

代码示例:

Java 代码 (SimpleAdder.java):

public class SimpleAdder {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.err.println("Usage: java SimpleAdder <num1> <num2>");
            System.exit(1); // 非零退出码表示错误
        }
        try {
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            int sum = a + b;
            // 将结果输出到标准输出,Node.js 可以捕获
            System.out.println(sum);
        } catch (NumberFormatException e) {
            System.err.println("Invalid number format.");
            System.exit(1);
        }
    }
}

编译并运行:

javac SimpleAdder.java

JavaScript 代码 (调用 Java):

const { spawn } = require('child_process');
// 启动 Java 进程
const javaProcess = spawn('java', ['SimpleAdder', '100', '200']);
let output = '';
javaProcess.stdout.on('data', (data) => {
  // 捕获标准输出
  output += data.toString();
});
javaProcess.stderr.on('data', (data) => {
  // 捕获标准错误
  console.error(`stderr: ${data}`);
});
javaProcess.on('close', (code) => {
  // Java 进程结束时触发
  console.log(`Java 进程退出,退出码: ${code}`);
  if (code === 0) {
    console.log(`计算结果是: ${output.trim()}`); // trim() 去掉末尾的换行符
  } else {
    console.error('Java 程序执行出错。');
  }
});

优点:

  • 简单直接: 不需要复杂的工具链,只要有 JDK 即可。
  • 隔离性好: Java 运行在独立的进程中,崩溃不会影响 Node.js 主进程。

缺点:

  • 性能开销: 每次调用都需要创建新的 Java 进程,开销很大,不适合高频调用。
  • 通信复杂: 需要手动处理数据序列化(如将 JS 对象转为字符串传给 Java)和进程间通信。

总结与对比

特性 场景一 (浏览器 JS -> 后端 Java) 场景二 (Node.js -> 本地 Java)
核心概念 客户端-服务器网络通信 跨语言运行时互操作
主要方式 RESTful API, WebSocket GraalVM, child_process
通信介质 HTTP 协议, TCP Socket JVM 内部对象调用, 进程间通信
性能 受网络延迟影响,但后端可优化 GraalVM: 极高,无进程开销
child_process: 低,进程创建开销大
适用场景 Web 应用、移动 App 后端、微服务 使用 Java 库的 Node.js 工具链、数据处理脚本、需要高性能计算的服务
技术栈 Spring Boot, Express.js, fetch, axios GraalVM, Node.js (JVM版), child_process
耦合度 低(前后端分离) GraalVM: 高(紧密耦合)
child_process: 中等

如何选择?

  • 如果你是 Web 开发者,想在网页上调用 Java 逻辑: 毫不犹豫地选择 RESTful API,这是行业标准,最灵活、最可扩展。
  • 如果你是 Node.js 开发者,想复用某个特定的 Java 库(比如一个强大的科学计算库):
    • 如果追求极致性能和原生体验,并且愿意尝试新技术,选择 GraalVM
    • 如果只是偶尔调用一次,或者 Java 库很简单,使用 child_process 是一个快速可行的方案。
分享:
扫描分享到社交APP
上一篇
下一篇