java中如何調(diào)用js
java中調(diào)用js
我們都知道腳本語言非常靈活,在處理某些問題的時(shí)候 Java 實(shí)現(xiàn)用十幾行來寫,用 js 可能不到十行就寫完,并且非常簡(jiǎn)潔,那么有沒有一種優(yōu)雅的方式將 Java 與腳本語言結(jié)合呢,在 Java SE6(代號(hào) Mustang)中,這將成為現(xiàn)實(shí)。
Nashorn,一個(gè)新的 JavaScript 引擎隨著 Java 8 一起公諸于世,它允許在 JVM 上開發(fā)運(yùn)行某些 JavaScript 應(yīng)用。Nashorn 就是 javax.script.ScriptEngine
的另一種實(shí)現(xiàn),并且它們倆遵循相同的規(guī)則,允許 Java 與 JavaScript 相互調(diào)用。
Mustang 的腳本引擎
JSR 233 為 Java 設(shè)計(jì)了一套腳本語言 API。這一套 API 提供了在 Java 程序中調(diào)用各種腳本語言引擎的接口。
任何實(shí)現(xiàn)了這一接口的腳本語言引擎都可以在 Java 程序中被調(diào)用。在 Mustang 的發(fā)行版本中包括了一個(gè)基于 Mozilla Rhino 的 JavaScript 腳本引擎,也就是說在 JavaSE6+ 的版本默認(rèn)可以直接調(diào)用執(zhí)行 JavaScript。
Mozilla Rhino
Rhino 是一個(gè)純 Java 的開源的 JavaScript 實(shí)現(xiàn)。他的名字來源于 O’Reilly 關(guān)于 JavaScript 的書的封面,是的,就是那本犀牛書…
Rhino 項(xiàng)目可以追朔到 1997 年,當(dāng)時(shí) Netscape 計(jì)劃開發(fā)一個(gè)純 Java 實(shí)現(xiàn)的 Navigator,為此需要一個(gè) Java 實(shí)現(xiàn)的 JavaScript —— Javagator。
它也就是 Rhino 的前身。起初 Rhino 將 JavaScript 編譯成 Java 的二進(jìn)制代碼執(zhí)行,這樣它會(huì)有最好的性能。后來由于編譯執(zhí)行的方式存在垃圾收集的問題并且編譯和裝載過程的開銷過大,不能滿足一些項(xiàng)目的需求,Rhino 提供了解釋執(zhí)行的方式。
隨著 Rhino 開放源代碼,越來越多的用戶在自己的產(chǎn)品中使用了 Rhino,同時(shí)也有越來越多的開發(fā)者參與了 Rhino 的開發(fā)并做出了很大的貢獻(xiàn)。如今 Rhino 將被包含在 Java SE 中發(fā)行,更多的 Java 開發(fā)者將從中獲益。
Rhino 提供了如下功能:
- 對(duì) JavaScript 1.5+ 的完全支持
- 直接在 Java 中使用 JavaScript 的功能
- 一個(gè) JavaScript shell 用于運(yùn)行 JavaScript 腳本
- 一個(gè) JavaScript 的編譯器,用于將 JavaScript 編譯成 Java 二進(jìn)制文件
支持的腳本語言
在 dev.java.net 可以找到官方的腳本引擎的實(shí)現(xiàn)項(xiàng)目。這一項(xiàng)目基于BSD License ,表示這些腳本引擎的使用將十分自由。
目前該項(xiàng)目已對(duì)包括 Groovy, JavaScript, Python, Ruby, PHP 在內(nèi)的二十多種腳本語言提供了支持。這一支持列表還將不斷擴(kuò)大。
在Java中的基本使用
在 Mustang 中對(duì)腳本引擎的檢索使用了工廠模式。
首先需要實(shí)例化一個(gè)工廠 : ScriptEngineManager
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngineManager 將在 Thread Context ClassLoader 的 Classpath 中根據(jù) jar 文件的 META-INF 來查找可用的腳本引擎。
它提供了 3 種方法來檢索腳本引擎:
// create engine by name ScriptEngine engine = factory.getEngineByName ("JavaScript"); // create engine by name ScriptEngine engine = factory.getEngineByExtension ("js"); // create engine by name ScriptEngine engine = factory.getEngineByMimeType ("application/javascript");
下面的代碼將會(huì)打印出當(dāng)前的 JDK 所支持的所有腳本引擎:
ScriptEngineManager factory = new ScriptEngineManager(); for (ScriptEngineFactory available : factory.getEngineFactories()) { System.out.println(available.getEngineName()); // 打印腳本具體名稱信息 System.out.println(available.getNames()); }
即可看到 [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript]
等輸出,說明可執(zhí)行 Js 腳本
執(zhí)行Js腳本
啥也別說了,都知道要干嘛,來打印句 HelloWorld 再說:
public class RunJavaScript { public static void main(String[] args){ ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName ("JavaScript"); engine.eval("print('Hello World')"); } }
如果你的 Js 有語法錯(cuò)誤,就會(huì)拋出 javax.script.ScriptException
異常
如果我們要解釋一些更復(fù)雜的腳本語言,或者想在運(yùn)行時(shí)改變?cè)撃_本該如何做呢?腳本引擎支持一個(gè)重載的 eval 方法,它可以從一個(gè) Reader 讀入所需的腳本,或者得到 Js 文件的絕對(duì)路徑,直接用自帶的 load 函數(shù)讀?。?/p>
ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName ("JavaScript"); engine.eval(new Reader("HelloWorld.js")); File file = new File(Main.class.getClassLoader().getResource("test.js").getFile()); engine.eval(new FileReader(file)); String scriptPath = Main.class.getClassLoader().getResource("test.js").getPath(); engine.eval("load('" + scriptPath + "')");
這里注意下 FileReader 中相對(duì)路徑到底是相對(duì)誰的(JVM 啟動(dòng)的位置),可使用 ClassLoader 讀取 src 下的文件
Java 程序?qū)?dòng)態(tài)的去讀取腳本文件并解釋執(zhí)行,這意味著,在程序運(yùn)行中你可以隨意修改 Js 文件里的代碼,會(huì)得到實(shí)時(shí)修改結(jié)果。
對(duì)于這一簡(jiǎn)單的 Hello World 腳本來說,IO 操作將比直接執(zhí)行腳本損失 20% 左右的性能(SE6 時(shí),個(gè)人測(cè)試),但他帶來的靈活性 — 在運(yùn)行時(shí)動(dòng)態(tài)改變代碼的能力,在某些場(chǎng)合是十分激動(dòng)人心的。
最后來看看完整的測(cè)試代碼:
package com.bfchengnuo.javascript; import javax.script.*; import java.io.FileNotFoundException; /** * Created by 冰封承諾Andy on 2017/12/30. */ public class Main { public static void main(String[] args) throws ScriptException, NoSuchMethodException, FileNotFoundException { ScriptEngineManager manager = new ScriptEngineManager(); System.out.println("當(dāng)前 JDK 支持的腳本語言引擎:"); for (ScriptEngineFactory available : manager.getEngineFactories()) { System.out.println(available.getEngineName()); System.out.println(available.getNames()); } ScriptEngine engine = manager.getEngineByName("JavaScript"); if (!(engine instanceof Invocable)) { System.out.println("Invoking methods is not supported."); return; } engine.eval("print('Hello World')"); Invocable inv = (Invocable) engine; // File file = new File(Main.class.getClassLoader().getResource("test.js").getFile()); // engine.eval(new FileReader(file)); String scriptPath = Main.class.getClassLoader().getResource("test.js").getPath(); engine.eval("load('" + scriptPath + "')"); // 獲取對(duì)象 Object calculator = engine.get("calculator"); int x = 3; int y = 4; Object addResult = inv.invokeMethod(calculator, "add", x, y); Object subResult = inv.invokeMethod(calculator, "subtract", x, y); Object mulResult = inv.invokeMethod(calculator, "multiply", x, y); Object divResult = inv.invokeMethod(calculator, "divide", x, y); System.out.println(addResult); System.out.println(subResult); System.out.println(mulResult); System.out.println(divResult); } }
對(duì)應(yīng)的 Js 文件:
var calculator = {}; calculator.add = function (n1, n2) { return n1 + n2}; calculator.subtract = function (n1, n2) {return n1 - n2}; calculator.multiply = function (n1, n2) {return n1 * n2}; calculator.divide = function (n1, n2) {return n1 / n2};
腳本語言與 Java 的通信
ScriptEngine 的 put 方法用于將一個(gè) Java 對(duì)象映射成一個(gè)腳本語言的變量?,F(xiàn)在有一個(gè) Java Class,它只有一個(gè)方法:
public class HelloWorld { String s = "Hello World"; public void sayHello(){ System.out.println(s); } }
接下來就是讓 Js 使用這個(gè)類!還是看代碼最實(shí)在:
public class TestPut { public static void main(String[] args) throws ScriptException { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("JavaScript"); HelloWorld hello = new HelloWorld(); engine.put("script_hello", hello); engine.eval("script_hello.sayHello()"); } }
首先我們實(shí)例化一個(gè) HelloWorld,然后用 put 方法將這個(gè)實(shí)例映射為腳本語言的變量 script_hello。那么我們就可以在 eval() 函數(shù)中像 Java 程序中同樣的方式來調(diào)用這個(gè)實(shí)例的方法。
使用 invokeFunction 來執(zhí)行 Js 函數(shù):
public class TestInv { public static void main(String[] args) throws Exception { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("JavaScript"); String script = "function say(first,second) { print(first +' '+ second); }"; engine.eval(script); Invocable inv = (Invocable) engine; inv.invokeFunction("say", "Hello", "Tony"); } }
這里使用了 ScriptEngine 的兩個(gè)可選接口之一 : Invocable,Invocable 表示當(dāng)前的 engine 可以作為函數(shù)被調(diào)用。
這里我們將 engine 強(qiáng)制轉(zhuǎn)換為 Invocable 類型,使用 invokeFunction 方法將參數(shù)傳遞給腳本引擎。invokeFunction 這個(gè)方法使用了可變參數(shù)的定義方式,可以一次傳遞多個(gè)參數(shù),并且將腳本語言的返回值作為它的返回值。
Invocable 接口還有一個(gè)方法用于從一個(gè) engine 中得到一個(gè) Java Interface 的實(shí)例,它接受一個(gè) Java 的 Interface 類型作為參數(shù),返回這個(gè) Interface 的一個(gè)實(shí)例。
也就是說你可以完全用腳本語言來寫一個(gè) Java Interface 的所有實(shí)現(xiàn)!然后直接調(diào)用使用返回的實(shí)現(xiàn)類就可以了!
engine.eval(script); Invocable inv = (Invocable) engine; MaxMin maxMin = inv.getInterface(MaxMin.class);
java 處理js返回值
- JDK11之前版本
package io.icefox; import java.util.Map; import java.util.TreeMap; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.SimpleBindings; import jdk.nashorn.api.scripting.ScriptObjectMirror; public class JSTest { public static ScriptEngine engine; private static String str; public static void main(String[] args) throws Exception { //獲取js引擎實(shí)例 ScriptEngineManager sem = new ScriptEngineManager(); engine=sem.getEngineByName("javascript"); getJsValue(); getObject(); putValue(); callJsFunction(); } //該函數(shù)測(cè)試Java獲取JS變量值的能力 public static void getJsValue() throws Exception{ str = " var msg='hello'; " + " var number = 123; " + " var array=['A','B','C']; " + " var json={ " + " 'name':'pd', " + " 'subjson':{ " + " 'subname':'spd' " + " ,'id':123 " + " } " + " }; "; //執(zhí)行語句 engine.eval(str); str="msg+=' world';number+=5"; //再次執(zhí)行 engine.eval(str); //獲取js變量msg(String類型) System.out.println(engine.get("msg")); //獲取js變量msg(int類型) System.out.println(engine.get("number")); //獲取js變量array(數(shù)組) ScriptObjectMirror array=(ScriptObjectMirror) engine.get("array"); //getSlot(int index)函數(shù)用于獲取下標(biāo)為index的值 System.out.println(array.getSlot(0)); //獲取js變量json(json類型) ScriptObjectMirror json=(ScriptObjectMirror) engine.get("json"); //get(String key)函數(shù)用于鍵key的值 System.out.println(json.get("name")); //獲取js變量subjson(嵌套json類型) ScriptObjectMirror subjson=(ScriptObjectMirror)json.get("subjson"); System.out.println(subjson.get("subname")); } //該函數(shù)測(cè)試Java與js對(duì)象 public static void getObject() throws Exception{ str = " var obj=new Object(); " + " obj.info='hello world'; " + " obj.getInfo=function(){ " + " return this.info; " + " }; "; engine.eval(str); //獲取對(duì)象 ScriptObjectMirror obj=(ScriptObjectMirror) engine.get("obj"); //輸出屬性 System.out.println(obj.get("info")); System.out.println(obj.get("getInfo")); str="obj.getInfo()"; //執(zhí)行方法 System.out.println(engine.eval(str)); } //java將變量導(dǎo)入js腳本 public static void putValue() throws Exception{ str="Math.pow(a,b)"; Map<String, Object>input=new TreeMap<>(); input.put("a",2); input.put("b",8); System.out.println(engine.eval(str,new SimpleBindings(input))); } //調(diào)用js函數(shù) public static void callJsFunction() throws Exception{ engine.eval("function add (a, b) {return a+b; }"); Invocable jsInvoke = (Invocable) engine; Object res = jsInvoke.invokeFunction("add", new Object[] { 10, 5 }); System.out.println(res); } }
- JDK11之后版本
package io.icefox; import org.openjdk.nashorn.api.scripting.ScriptObjectMirror; import java.util.Map; import java.util.TreeMap; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.SimpleBindings; public class JSTest { public static ScriptEngine engine; private static String str; //該函數(shù)測(cè)試Java獲取JS變量值的能力 public static void getJsValue() throws Exception{ str = " var msg='hello'; " + " var number = 123; " + " var array=['A','B','C']; " + " var json={ " + " 'name':'pd', " + " 'subjson':{ " + " 'subname':'spd' " + " ,'id':123 " + " } " + " }; "; //執(zhí)行語句 engine.eval(str); str="msg+=' world';number+=5"; //再次執(zhí)行 engine.eval(str); //獲取js變量msg(String類型) System.out.println(engine.get("msg")); //獲取js變量msg(int類型) System.out.println(engine.get("number")); //獲取js變量array(數(shù)組) ScriptObjectMirror array=(ScriptObjectMirror) engine.get("array"); //getSlot(int index)函數(shù)用于獲取下標(biāo)為index的值 System.out.println(array.getSlot(0)); //獲取js變量json(json類型) ScriptObjectMirror json=(ScriptObjectMirror) engine.get("json"); //get(String key)函數(shù)用于鍵key的值 System.out.println(json.get("name")); //獲取js變量subjson(嵌套json類型) ScriptObjectMirror subjson=(ScriptObjectMirror)json.get("subjson"); System.out.println(subjson.get("subname")); } //該函數(shù)測(cè)試Java與js對(duì)象 public static void getObject() throws Exception{ str = " var obj=new Object(); " + " obj.info='hello world'; " + " obj.getInfo=function(){ " + " return this.info; " + " }; "; engine.eval(str); //獲取對(duì)象 ScriptObjectMirror obj=(ScriptObjectMirror) engine.get("obj"); //輸出屬性 System.out.println(obj.get("info")); System.out.println(obj.get("getInfo")); str="obj.getInfo()"; //執(zhí)行方法 System.out.println(engine.eval(str)); } //java將變量導(dǎo)入js腳本 public static void putValue() throws Exception{ str="Math.pow(a,b)"; Map<String, Object>input=new TreeMap<>(); input.put("a",2); input.put("b",8); System.out.println(engine.eval(str,new SimpleBindings(input))); } //調(diào)用js函數(shù) public static void callJsFunction() throws Exception{ engine.eval("function add (a, b) {return a+b; }"); Invocable jsInvoke = (Invocable) engine; Object res = jsInvoke.invokeFunction("add", new Object[] { 10, 5 }); System.out.println(res); } public static void main(String[] args) throws Exception { //獲取js引擎實(shí)例 ScriptEngineManager sem = new ScriptEngineManager(); engine=sem.getEngineByName("javascript"); getJsValue(); getObject(); putValue(); callJsFunction(); } }
- JDK11之后移除了
jdk.nashorn.api.scripting.ScriptObjectMirror
使用JDK15之后版本需要添加maven依賴
使用org.openjdk.nashorn.api.scripting.ScriptObjectMirror
<dependency> <groupId>org.openjdk.nashorn</groupId> <artifactId>nashorn-core</artifactId> <version>15.3</version> </dependency>
其他
其他的還可以對(duì) Js 進(jìn)行編譯、在 Js 中調(diào)用 Java 的代碼,但感覺用的不多,最常用的還是 Java 調(diào)用 Js 的方法
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
如何在Redis中實(shí)現(xiàn)分頁排序查詢過程解析
這篇文章主要介紹了如何在Redis中實(shí)現(xiàn)分頁排序查詢過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07用Maven插件生成Mybatis代碼的實(shí)現(xiàn)方法
本文主要介紹 Maven插件生成Mybatis代碼,現(xiàn)在做開發(fā)的朋友有好多用Maven 來管理代碼,這里給大家舉個(gè)例子,有需要的同學(xué)可以看下2016-07-07SpringBoot整合Minio實(shí)現(xiàn)上傳文件的完整步驟記錄
MinIO是一個(gè)基于Apache License v2.0開源協(xié)議的對(duì)象存儲(chǔ)服務(wù),它兼容亞馬遜S3云存儲(chǔ)服務(wù)接口,非常適合于存儲(chǔ)大容量非結(jié)構(gòu)化的數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于SpringBoot整合Minio實(shí)現(xiàn)上傳文件的完整步驟,需要的朋友可以參考下2022-05-05Dependency ‘XXX:‘ not found問題的三步解決
這篇文章主要介紹了Dependency ‘XXX:‘ not found問題的三步解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01java通過URLClassLoader類加載器加載外部jar代碼示例
ClassLoader翻譯過來就是類加載器,普通的java開發(fā)者其實(shí)用到的不多,但對(duì)于某些框架開發(fā)者來說卻非常常見,下面這篇文章主要給大家介紹了關(guān)于java通過URLClassLoader類加載器加載外部jar的相關(guān)資料,需要的朋友可以參考下2024-01-01SpringBoot配置開發(fā)環(huán)境的詳細(xì)步驟(JDK、Maven、IDEA等)
文章介紹了如何配置SpringBoot開發(fā)環(huán)境,包括安裝JDK、Maven和IDEA,并提供了詳細(xì)的步驟和配置方法,感興趣的朋友一起看看吧2024-12-12