无框架场景下如何通过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
相关产品推荐
相关产品推荐

