- 前端浏览器环境中的 JavaScript 调用后端 Java 代码
- JavaScript 引擎(如 Node.js)中直接调用 Java 代码
前端浏览器环境中的 JavaScript 调用后端 Java 代码
这是最常见、最标准的场景,浏览器中的 JavaScript 运行在客户端,而 Java 代码运行在服务器端,它们之间不能直接调用,必须通过网络通信来完成。

核心思想:客户端-服务器架构
前端 JS 作为客户端,向后端 Java 应用发送一个请求(通常是 HTTP 请求),后端 Java 应用接收到请求后执行相应的业务逻辑,然后将结果以某种格式(如 JSON)返回给前端。
主要实现方式
RESTful API (最主流、最推荐的方式)
这是目前业界最标准、最通用的做法,后端 Java 应用暴露一组 URL 接口(端点),前端通过 HTTP 方法(GET, POST, PUT, DELETE 等)来调用这些接口。
工作流程:
-
后端 (Java):
(图片来源网络,侵删)- 使用 Java Web 框架(如 Spring Boot)创建一个 Web 服务器。
- 定义 Controller 类,使用
@RestController或@Controller注解。 - 在方法上使用
@RequestMapping或@GetMapping、@PostMapping等注解来映射 URL。 - 方法内部执行 Java 代码(如数据库操作、业务逻辑计算),并返回数据,框架会自动将返回的对象序列化为 JSON 格式。
-
前端 (JavaScript):
- 使用
fetchAPI 或者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):

// 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 更合适。
工作流程:
- 前端 JS 和后端 Java 建立一个持久化的 WebSocket 连接。
- 连接建立后,任何一方都可以主动向另一方发送消息。
- 后端 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。
工作流程:
- 安装 GraalVM: 安装 GraalVM 并设置
JAVA_HOME。 - 安装 Node.js: 在 GraalVM 的
bin目录下安装 Node.js (gu install nodejs)。 - 编写 Java 代码: 编写你想要被调用的 Java 类。
- 编写 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 模块。
工作流程:
- Node.js: 通过
spawn或exec方法启动一个新的 Java 进程,并传递参数。 - Java: 编写一个可以接收命令行参数或标准输入的 Java 程序。
- 通信:
- 标准输入/输出: Node.js 向 Java 进程的
stdin写入数据,从stdout读取结果。 - 退出码: Java 进程执行完毕后,通过退出码表示成功或失败。
- 标准输入/输出: Node.js 向 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是一个快速可行的方案。
