You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无框架场景下如何通过JS调用Java方法运行本地localhost站点

无框架JS调用Java方法实现方案

核心实现逻辑:基于JDK内置的HTTP服务能力搭建后端,无需引入Tomcat、Spring等任何Web/开发框架,前端用原生JS通过HTTP请求调用后端接口,静态资源由后端HTTP服务直接返回,本地localhost可直接访问。

一、IDEA项目目录结构

按如下结构组织文件,所有代码均放在src目录下:

项目根目录
├── src
│   ├── Main.java # 服务启动主类、包含你的testJS测试方法
│   ├── ReimbursementClaim.java # 报销单实体类
│   ├── AirPlaneReceipt.java # 机票发票实体类
│   ├── HotelReceipt.java # 酒店发票实体类
│   ├── TaxiReceipt.java # 出租车发票实体类
│   ├── TrainReceipt.java # 火车票实体类
│   ├── OtherReceipt.java # 其他发票实体类
│   ├── index.html # 前端入口页面
│   └── xxx.js # 你需要编写的调用Java方法的JS文件
└── out # IDEA编译输出目录,无需手动创建

二、后端代码实现(纯JDK,无任何第三方依赖)

1. 实体类编写

所有实体类为普通POJO,添加对应属性的getter/setter即可:

  • ReimbursementClaim 类包含属性:int numberOfDaysForDailyAllowance、int personalCarMileage、List<AirPlaneReceipt> airPlaneReceiptList、List<HotelReceipt> hotelReceiptList、List<TaxiReceipt> taxiReceiptList、List<TrainReceipt> trainReceiptList、List<OtherReceipt> otherReceiptList
  • 其余发票实体类可先创建空类,不影响测试流程。

2. 主服务类代码

Main.java 代码如下,内置HTTP服务同时处理接口请求和静态资源返回:

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class Main {
    // 你提供的测试方法
    public float testJS(ReimbursementClaim reimbursementClaim){
        int numberOfDaysForDailyAllowance = 10;
        int personalCarMileage = 10;
        List<AirPlaneReceipt> airPlaneReceiptList = new ArrayList<>();
        List<HotelReceipt> hotelReceiptList = new ArrayList<>();
        List<TaxiReceipt> taxiReceiptList = new ArrayList<>();
        List<TrainReceipt> trainReceiptList = new ArrayList<>();
        List<OtherReceipt> otherReceiptList = new ArrayList<>();
        reimbursementClaim.setNumberOfDaysForDailyAllowance(numberOfDaysForDailyAllowance);
        reimbursementClaim.setPersonalCarMileage(personalCarMileage);
        reimbursementClaim.setAirPlaneReceiptList(airPlaneReceiptList);
        reimbursementClaim.setHotelReceiptList(hotelReceiptList);
        reimbursementClaim.setTaxiReceiptList(taxiReceiptList);
        reimbursementClaim.setTrainReceiptList(trainReceiptList);
        reimbursementClaim.setOtherReceiptList(otherReceiptList);
        return getTotalReimbursement(reimbursementClaim);
    }

    // 替换为你自己的报销金额计算逻辑
    private float getTotalReimbursement(ReimbursementClaim claim) {
        // 测试逻辑:每日补贴100元,自驾每公里补贴2元,预期返回10*100 + 10*2 = 1020
        return claim.getNumberOfDaysForDailyAllowance()*100f + claim.getPersonalCarMileage()*2f;
    }

    public static void main(String[] args) throws IOException {
        Main server = new Main();
        // 监听80端口,端口占用可自行修改为8080等未被占用的端口
        HttpServer httpServer = HttpServer.create(new InetSocketAddress(80), 0);

        // 接口路由:处理JS发起的testJS方法调用请求
        httpServer.createContext("/api/testJS", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
                if ("POST".equalsIgnoreCase(exchange.getRequestMethod())) {
                    // 测试场景下直接构造参数调用方法,后续可自行扩展请求参数解析逻辑
                    ReimbursementClaim claim = new ReimbursementClaim();
                    float result = server.testJS(claim);
                    // 拼接返回结果
                    String response = "{\"code\":200,\"data\":"+result+"}";
                    exchange.sendResponseHeaders(200, response.getBytes(StandardCharsets.UTF_8).length);
                    OutputStream os = exchange.getResponseBody();
                    os.write(response.getBytes(StandardCharsets.UTF_8));
                    os.close();
                } else {
                    exchange.sendResponseHeaders(405, 0);
                }
                exchange.close();
            }
        });

        // 静态资源路由:返回html、js文件
        httpServer.createContext("/", new HttpHandler() {
            @Override
            public void handle(HttpExchange exchange) throws IOException {
                String requestPath = exchange.getRequestURI().getPath();
                if ("/".equals(requestPath)) {
                    requestPath = "/index.html";
                }
                // 直接读取src目录下的静态文件
                String filePath = "src" + requestPath;
                File file = new File(filePath);
                // 拦截java源码文件请求,避免源码泄露
                if (filePath.endsWith(".java")) {
                    exchange.sendResponseHeaders(403, 0);
                    exchange.close();
                    return;
                }
                if (file.exists() && file.isFile()) {
                    // 设置对应资源的Content-Type
                    String contentType = "text/html;charset=utf-8";
                    if (requestPath.endsWith(".js")) {
                        contentType = "application/javascript;charset=utf-8";
                    } else if (requestPath.endsWith(".css")) {
                        contentType = "text/css;charset=utf-8";
                    }
                    exchange.getResponseHeaders().add("Content-Type", contentType);
                    FileInputStream fis = new FileInputStream(file);
                    exchange.sendResponseHeaders(200, file.length());
                    OutputStream os = exchange.getResponseBody();
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = fis.read(buffer)) != -1) {
                        os.write(buffer, 0, len);
                    }
                    fis.close();
                    os.close();
                } else {
                    String notFound = "404 Not Found";
                    exchange.sendResponseHeaders(404, notFound.getBytes().length);
                    exchange.getResponseBody().write(notFound.getBytes());
                }
                exchange.close();
            }
        });

        httpServer.start();
        System.out.println("服务启动成功,访问 http://localhost 即可打开页面");
    }
}

三、前端代码实现

1. index.html 入口页面

放在src目录下,代码如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Java方法调用测试</title>
</head>
<body>
    <h1>报销计算测试</h1>
    <button id="callBtn">点击调用后端testJS方法</button>
    <p>接口返回结果:<span id="result"></span></p>
    <script src="xxx.js"></script>
</body>
</html>

2. xxx.js 调用逻辑

放在src目录下,通过原生fetch接口发起请求调用Java方法:

const callBtn = document.getElementById('callBtn');
const resultDom = document.getElementById('result');

callBtn.addEventListener('click', async () => {
    try {
        const response = await fetch('/api/testJS', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            // 后续需要传参时,将参数放在body中通过JSON.stringify序列化即可
            body: JSON.stringify({})
        });
        const resData = await response.json();
        resultDom.textContent = resData.data;
    } catch (err) {
        resultDom.textContent = '调用失败:' + err.message;
    }
});

四、IDEA运行步骤

  • 打开IDEA,找到Main.java文件,右键点击main方法,选择Run 'Main.main()'启动服务
  • 注意:Windows/macOS系统下监听80端口需要管理员权限,如果启动报端口绑定错误,要么用管理员身份启动IDEA,要么将代码中的监听端口改为8080等1024以上的端口,访问时对应使用http://localhost:8080即可
  • 控制台打印服务启动成功提示后,打开浏览器输入对应localhost地址,即可访问index.html页面,点击按钮就能触发JS调用Java的testJS方法,拿到计算结果。

说明

  • 整套实现没有引入任何开发框架,后端能力完全基于JDK内置API实现,前端为原生HTML/CSS/JS,符合约束要求
  • 测试场景下JSON处理采用简单字符串拼接,后续如果需要传递复杂参数,可自行扩展简单的JSON解析逻辑,也可引入轻量级JSON工具包(不属于开发框架范畴,不违反约束)

内容的提问来源于stack exchange,提问作者Mateusz Janus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 16:12:24