在JVM跑JavaScript腳本 | Oracle GraalJS 簡介與實踐

這是2024年初的 GraalVM 系列博文,當時寫了大綱,知道一年半后的現在才得以完成發布😄

1、概述

實話說,標題的場景為小眾需求,日常開發基本用不到,我是最近在做一個低代碼輪子玩具 app-meta 需要實現 FaaS(Function as a Service)功能才接觸到 JS 引擎。還有如下的場景會用的上:

  • 調用 js 特有的函數(java 體系沒有更好的選擇)
  • 動態執行代碼(代碼邏輯隨時可修改,這一塊腳本語言有天然優勢)
  • 需要利用腳本語言擴展 Java 功能

我計劃針對在JVM跑JavaScript腳本寫系列的文章:

  1. 在JVM跑JavaScript腳本 | Oracle GraalJS 簡介與實踐
  2. 在JVM跑JavaScript腳本 | FaaS架構簡單實現

溫馨提示:文章內容較長,可按需定位章節閱讀😃

1.1、JVM 下 JS 引擎

內置引擎

引擎所屬 JDK 版本基于的 ECMAScript 版本備注
RhinoJDK 6 及之前(Java 1.6)ES3(部分 ES5)由 Mozilla 用 Java 編寫,最早的 JVM JS 引擎,速度慢但易集成。
NashornJDK 8 ~ JDK 14ES5.1(少量 ES6 特性)Oracle 開發,性能較 Rhino 高,支持 Java <-> JS 互操作;JDK 15 開始移除。

外部高性能引擎

引擎運行機制特點項目鏈接
Graal.jsGraalVM 提供的 JS 實現支持 ES2022 及后續,性能高,可與 Java 混合調用,無需 JNI 手寫Graal.js 官方
JavetJNI 調用 V8 引擎完整支持現代 JS/Node API,性能接近 Node.jsJavet
Duktape-Java嵌入 Duktape 引擎小巧、易嵌入、啟動快,適合輕量腳本執行Duktape-Java
QuickJS-JavaJNI 調用 QuickJS支持最新 JS 特性(ES2020+),內存占用小QuickJS-Java

再后來,GraalVM 橫空出世,它是 Oracle Labs 開發的一款 高性能、多語言虛擬機,目標是在 同一個運行時 下高效運行多種編程語言(Java、JavaScript、Python、Ruby、R、LLVM-based 語言、WebAssembly 等),并且實現這些語言之間的無縫互操作。

主要組件

組件作用
Graal Compiler高性能 JIT 編譯器,可替代 HotSpot 的 C2 編譯器。
GraalJS在 GraalVM 上運行的 JavaScript/Node.js 實現,支持現代 ECMAScript 規范。
Truffle一套多語言實現框架,用于開發新語言運行時。
Native ImageAOT 編譯工具,將 Java 應用打包成本地二進制可執行文件。
Polyglot API提供跨語言調用的統一 API。

今天我們的主角就是 GraalJS。

1.2、 GraalJS 簡介

GraalJS: A ECMAScript 2022 compliant JavaScript implementation built on GraalVM. With polyglot language interoperability support. Running Node.js applications!

翻譯過來就是,GraalJS 是基于 GraalVM 構建,兼容 ECMAScript 2022 語法的 JavaScript 實現,能夠運行 Node.js 應用,同時支持 polyglot (多語言互操作)。

為什么選擇它?

最主要原因是它支持較新的 js 語法,有大公司背書,還考慮到 GraalVM 還支持其他腳本語言(如 python),有利于以后的功能擴展。


2、開始使用

📦 依賴引入

此處以 maven 為例

<!-- 增加 GraalJS 依賴,graalvm.version 替換為最新的版本號即可 -->
<properties><graal.version>24.2.1</graal.version>
</properties><dependencies><dependency><groupId>org.graalvm.polyglot</groupId><artifactId>polyglot</artifactId><version>${graal.version}</version></dependency><dependency><groupId>org.graalvm.polyglot</groupId><artifactId>js</artifactId><version>${graal.version}</version><type>pom</type></dependency>
</dependencies>

👋 慣例 Hello World

import org.graalvm.polyglot.Context;public class GraalJSDemo {public static void main(String[] args) {try (Context context = Context.create()) {context.eval("js", "console.log(`來自 GraalJS 的問候!Time=${Date.now()}`)");}}
}

代碼淺析

  • ContextGraalVM Polyglot API 的核心類。它代表一個“多語言執行上下文”(Execution Context),里面可以執行不同語言的代碼,比如 "js"(JavaScript)、"python""ruby" 等。

  • 每個 Context 可以看成是一個沙箱(sandbox),里面有獨立的全局變量、函數等運行環境。

  • 使用 try-with-resources,保證 Context 在使用結束后會自動關閉并釋放資源(例如內存、線程等)。

  • Context.create() 會創建一個默認的多語言上下文:

    • 默認啟用 JavaScript、Python 等 GraalVM 已安裝的語言(如果你是 GraalVM Standard Edition,可能默認只開啟 JavaScript)。
    • 你也可以用 Context.create("js") 來只創建 JS 運行環境(更精簡)。
  • eval(languageId, sourceCode) 用來在指定語言中執行一段代碼。

    • languageId"js" 代表執行 JavaScript 代碼。
    • sourceCode"console.log('Hello from GraalJS!')" 是要運行的 JavaScript 源碼。
  • 在 GraalVM 里,console.log 是 Graal.js 提供的一個 JS 全局函數,輸出到 Java 的標準輸出(System.out)。

執行后,你會在 Java 控制臺看到:

💱 參數傳遞

我們可以在 JavaScript 里定義函數,然后從 Java 調用它,傳遞參數。這種方式適合當腳本是函數而不是全局執行代碼

/*** 構建一個 JS 函數,支持傳遞參數并得到結果*/
@Test
public void funWithParams(){try(Context ctx = Context.create(JS)){// 構建函數對象Value addFunc = ctx.eval(JS, "(x, y)=> x+y");// 傳遞參數調用它int result = addFunc.execute(100, 100).asInt();System.out.println("執行 100+100 函數,結果="+result);}
}

🔌 全局變量

全局變量就是給 JS 引擎賦予全局可訪問的值,類似于 HTML 中的 window😄。這里就需要用到Bindings組件。GraalVM 的 Bindings 類似于一個共享的變量表,你可以在 Java 里放值,JS 直接讀取。同時參數類型也會自動映射(Java 數字 → JS 數字)👍。

定義 Java 類

public class JavaLogger {// 定義時間格式器(HH表示24小時制,hh表示12小時制)DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");private void log(String level, String msg){String time = LocalTime.now().format(formatter);System.out.printf("[JAVA] %s %-5s %s%n", time, level, msg);}public void info(String msg){ log("INFO", msg); }public void debug(String msg){ log("DEBUG", msg); }public void error(String msg){ log("ERROR", msg); }
}
private void printValue(Value value){System.out.printf("%n------------------------ 腳本返回值 ------------------------%n%s", value);
}/*** 設置全局變量*/
@Test
public void bindings(){/*** 自定義 Java 、JS 互通規則,按需開啟對應的權限-*/HostAccess hostAccess = HostAccess.newBuilder()//允許不受限制地訪問所有公共構造函數、公共類的方法或字段.allowPublicAccess(true)//允許客戶端語言實現任何 Java 接口.allowAllImplementations(false)//允許客戶端語言實現(擴展)任何 Java 類.allowAllClassImplementations(false)//允許訪問數組.allowArrayAccess(false)//允許訪問 List.allowListAccess(false)//允許客戶應用程序以緩沖區元素的形式訪問 ByteBuffers.allowBufferAccess(false)//允許客戶應用程序使用迭代器將可迭代對象作為值進行訪問.allowIterableAccess(false)//允許客戶應用程序將迭代器作為迭代器值進行訪問。.allowIteratorAccess(false)//允許客戶應用程序以哈希值形式訪問 Map 對象.allowMapAccess(true)//允許客戶應用程序繼承對允許方法的訪問權限.allowAccessInheritance(false).build();// 使用自定義 HostAccess 構建 Contexttry(Context ctx=Context.newBuilder(JS).allowHostAccess(hostAccess).build()){Value global =  ctx.getBindings(JS);global.putMember("UUID", UUID.randomUUID().toString());// 傳遞 Map 鍵值對global.putMember("User",Map.of("name", "集成顯卡","url", "https://github.com/0604hx"));// 放置對象示例global.putMember("log", new JavaLogger());String script = """log.info(`開始執行 JS 腳本,UUID=${UUID}`)log.debug(`測試 debug 日志...`)log.error(`測試 error 日志...`)let result = { time: Date.now(), name: User.name, uuid: UUID }result""";printValue(ctx.eval(JS, script));}
}


關于 HostAccess 權限,可以查看官方文檔:HostAccess.Builder。

?? 安全管理

allowAllAccess

Context.allowAllAccess 是 GraalVM Polyglot API 里 Context.newBuilder() 的一個配置,用來放開 Java 與其他語言之間的所有訪問限制。

如果通過context.allowAllAccess(true),則表示:“我信任這個腳本,允許它干任何事,包括直接操作 Java 類、方法、字段,甚至文件系統和網絡”。對于不明來源不明作用的腳本,這是非常危險的!所以,該項是默認 false。除非特殊情況,我都強烈建議關閉它。在腳本真要調用什么 Java 代碼,可以通過全局對象來實現。

開啟 allowAllAccess(true) 后:

  • 解除幾乎所有安全限制
  • JS / Python / 其他腳本語言可以直接調用 Java API
  • 可以訪問文件、網絡、系統屬性等

例子(JS 調用 Java 類):

try (Context context = Context.newBuilder("js").allowAllAccess(true).build()) {context.eval("js", """const File = Java.type('java.io.File');let f = new File('test.txt');console.log("Absolute Path:", f.getAbsolutePath());""");
}

如果沒有 allowAllAccess(true),上面會拋異常:

java.lang.SecurityException: Access to host classes is not allowed.

allowIO

默認情況下, Context 是不允許執行 I/O 操作(輸入輸出)的,包括讀寫文件、訪問標準輸入輸出流、打開網絡連接等。必要情況可通過context.allowIO(IOAccess.ALL)開啟。

附錄

源代碼

本文所有源代碼均在:?Java實用示例合集-GraalJS ?

參考資料

  • 全棧虛擬機GraalVM初體驗
  • clever-graaljs:基于 graaljs 的高性能js腳本引擎,適合各種需要及時修改代碼且立即生效的場景,如:ETL工具、動態定時任務、接口平臺、工作流執行邏輯。 fast-api 就是基于clever-graaljs開發的接口平臺,可以直接寫js腳本開發Http接口,簡單快速!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/95741.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/95741.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/95741.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于 EC 數據與大模型技術實現天氣預報:從數據到上線的全棧方法

1. 先校準“EC 數據”與“AI 預報”的語境 EC 數據家族(常用) IFS/HRES:確定性全球模式,水平分辨率約 9 km,常用預報范圍 10 天; IFS/ENS:51 成員集合預報,提供 15 天概率信息; ERA5:再分析數據,小時級、0.25,可追溯至 1940 年,用作訓練/評測黃金基準。 AI 預報…

迭代器模式及優化

迭代器模式&#xff08;Iterator Pattern&#xff09;是一種行為型設計模式&#xff0c;用于提供一種統一的方式遍歷聚合對象&#xff08;如集合、容器&#xff09;中的元素&#xff0c;而無需暴露對象的內部實現細節。它將遍歷邏輯與聚合對象分離&#xff0c;使得遍歷操作可以…

純Qt手撕gb28181協議/gb28181協議服務端/gb28181協議設備端/gb28181設備模擬器/gb28181虛擬監控設備

一、前言說明 搞完onvif設備模擬器&#xff0c;總想著把28181設備模擬也實現&#xff0c;因為之前已經花了大力氣把28181平臺軟件端實現了&#xff0c;為了實現這個組件&#xff0c;頭發掉了一大把&#xff0c;專門把國標文檔看了好幾遍&#xff0c;逐行閱讀&#xff0c;針對需…

【滲透實戰】無下載器環境(curl/wget)下玩轉 Metasploit 自動利用

1. 背景與問題場景 在滲透測試或漏洞利用中&#xff0c;Metasploit&#xff08;MSF&#xff09;是業界最常用的框架之一。 其許多 RCE&#xff08;遠程代碼執行&#xff09;模塊在落地 payload&#xff08;如 Meterpreter 或反彈 shell&#xff09;時&#xff0c;采用了 CMD St…

jd-hotkey探測熱點key

對任意突發性的無法預先感知的熱點數據&#xff0c;包括并不限于熱點數據&#xff08;如突發大量請求同一個商品&#xff09;、熱用戶&#xff08;如惡意爬蟲刷子&#xff09;、熱接口&#xff08;突發海量請求同一個接口&#xff09;等&#xff0c;進行毫秒級精準探測到。然后…

C#WPF實戰出真汁07--【系統設置】--菜品類型設置

1、菜品設置介紹 菜品設置跟餐桌設置的功能目的是相同的&#xff0c;包括了新增&#xff0c;刪除&#xff0c;編輯&#xff0c;分頁&#xff0c;查詢&#xff0c;重置&#xff0c;全選&#xff0c;全消&#xff0c;列表功能&#xff0c;實現流程也是布局設計&#xff0c;后臺邏…

aave v3 存款與借款利息的計算方式

本文只涉及到利率計算的數學原理&#xff0c;不作源碼解析:存款首先我們假設小明在aave里面存了10000usdt&#xff0c;存的時候年化收益率是5%,那么半年后其存款的利息是多少呢?常規的計算方式如下:利息10000*5%*(存款的時長/一年的時長)這么做有什么問題呢&#xff1f;假設現…

Windows MCP.Net:基于.NET的Windows桌面自動化MCP服務器深度解析

&#x1f4cb; 目錄 項目概述 技術架構深度解析 核心功能模塊詳解 代碼實現分析 使用場景與實戰案例 性能優化與最佳實踐 擴展開發指南 總結與展望 項目概述 什么是Windows-MCP.Net&#xff1f; Windows MCP.Net是一個基于.NET 10.0開發的Windows桌面自動化MCP&…

Boost.Asio學習(7):Boost.Beast實現簡易http服務器

namespace beast boost::beast;beast::flat_buffer是一個用于 Boost.Asio 和 Boost.Beast 網絡讀寫的緩沖區實現。專為 一次性順序讀取 / 消費 場景設計&#xff0c;比 std::string 或 std::vector 高效&#xff0c;因為它是扁平內存結構&#xff08;contiguous memory&#x…

深入解析JVM內存區域劃分:從理論到實踐

Java虛擬機&#xff08;JVM&#xff09;是Java程序運行的核心環境&#xff0c;它負責管理內存分配、垃圾回收、字節碼執行等關鍵任務。理解JVM的內存區域劃分&#xff0c;對于優化Java應用性能、排查內存問題&#xff08;如OutOfMemoryError、StackOverflowError&#xff09;至…

滑窗|貪心|?滾動數組

lc17.08pair按身高升序、相同時體重降序排序結果是找體重序列的最長遞增子序列長度核心&#xff1a;轉化為二維最長遞增子序列問題求解vector<int> dp;for (auto& p : hw) {int w p.second;auto it lower_bound(dp.begin(), dp.end(), w);if (it dp.end()) {dp.pu…

深入理解數據庫架構:從原理到實踐的完整指南

一、數據庫存儲架構的多維度分類體系 1.1 基于數據組織方式的存儲架構分類 數據庫的存儲架構從根本上決定了其性能特征、適用場景和擴展能力。理解不同的數據組織方式是選擇合適數據庫技術的基礎&#xff0c;這種分類不僅反映了技術實現的差異&#xff0c;更體現了對不同業務需…

體彩排列三第2025218期號碼分析

大家好&#xff0c;本人蔡楚門來此平臺分享一下本期得經驗和思路&#xff0c;希望能夠給大家帶來好的運氣和靈感&#xff01;體彩排列三第2025218期號碼分析&#xff0c;大小號碼數字分析&#xff0c;上期開出全小號碼最多&#xff0c;最近兩期的開獎號碼全部都是全小號碼最多&…

java設計模式之迪米特法則介紹與說明

一、核心概念與目標 基本定義 迪米特法則的核心思想是&#xff1a;一個對象應該對其他對象盡可能少地了解&#xff0c;僅與直接關聯的對象&#xff08;即“朋友”&#xff09;通信&#xff0c;避免與“陌生人”產生直接交互。 直接朋友&#xff1a;包括當前對象的成員變量、方法…

2024-2025華為ICT大賽中國區 實踐賽昇騰AI賽道(高職組)全國總決賽 理論部分真題+解析

Part 1 昇騰AI全棧系統模塊(共6題)&#xff1a;1、許多計算芯片可以設計作為人工智能的計算芯片&#xff0c;但不同的芯片計算性能不同&#xff0c;昇騰計算芯片是一種()芯片。(單選題)A.CPU B.GPU C. NPU D.TPU正確答案&#xff1a;C解析&#xff1a;A項CPU中央處理器的架…

網絡安全和基礎設施安全局 (CISA) 表示微分段不再是可選的

網絡安全和基礎設施安全局 (CISA) 最近發布了一系列指導文件中的第一份&#xff0c;旨在幫助聯邦機構實施微分段&#xff0c;作為其零信任架構 (ZTA) 戰略的一部分&#xff0c;以遵守2022 年白宮的授權。 該文件《零信任中的微分段&#xff0c;第一部分&#xff1a;介紹和規劃…

Spring Boot SseEmitter 重復請求問題深度分析與解決方案

1. 前言 在使用 Spring Boot 開發流式接口(Server-Sent Events)時,我們遇到了一個令人困惑的問題:每次 SseEmitter 完成后,都會觸發第二次請求,導致重復請求檢測機制誤報。本文將詳細記錄問題的發現、分析過程以及最終的解決方案。 2. 系統架構背景 2.1 請求處理架構 …

心路歷程-三個了解敲開linux的大門

學習前都愛嘮叨一番&#xff1a; 了解一下&#xff1a;互聯網的發展是離不開服務器的&#xff0c;而服務器的系統主流的還是Linux&#xff1b;這個是有數據進行支撐的&#xff1b;這個只是作為了解而已&#xff0c;我們并不買課&#xff0c;也不做什么買賣的行為&#xff0c;僅…

關于“雙指針法“的總結

筆者這些天終于達成了只狼的全成就&#xff0c;甚是歡喜。然而樂極生悲&#xff0c;最近做了些算法題&#xff0c;竟沒有一道靠自己做出來。感覺算法題常常用到“雙指針法”呢……為什么到現在我還是做不出來這些算法題……今天就來試著總結一下它的使用場景吧。快慢指針法又名…

基于51單片機的智能吊燈

基于 51 單片機的智能吊燈設計與實現論文簡綱一、引言1.1 研究背景與意義闡述傳統照明設備在節能性、智能化方面的不足&#xff0c;結合智能家居產業發展趨勢&#xff0c;說明設計基于 51 單片機的智能吊燈對提升生活便利性、降低能耗的現實意義。1.2 國內外研究現狀簡要介紹當…