在java中执行js脚本,首先感觉这种操作有点无意义,但是存在即合理,作者要设计这个功能应该是有他的使用场景。java中支持使用jsp写网页,估计是这个原因才有了脚本引擎。
我们可以通过下面的代码查看目前支持的脚本语言:
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import java.util.List;
/**
* 腳本语言
* @Author xingo
* @Date 2023/11/10
*/
public class JavaScriptDemo {
public static void main(String[] args) {
// 支持的腳本引擎列表
ScriptEngineManager manager = new ScriptEngineManager();
List<ScriptEngineFactory> factories = manager.getEngineFactories();
for (ScriptEngineFactory f : factories) {
System.out.println("egine name : " + f.getEngineName() +
"\nengine version : " + f.getEngineVersion() +
"\nlanguage name : " + f.getLanguageName() +
"\nlanguage version : " + f.getLanguageVersion() +
"\nnames : " + f.getNames() +
"\nmime : " + f.getMimeTypes() +
"\nextension : " + f.getExtensions());
}
}
}
可以看到输出内容如下:
egine name : Oracle Nashorn
engine version : 1.8.0_161
language name : ECMAScript
language version : ECMA - 262 Edition 5.1
names : [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript]
mime : [application/javascript, application/ecmascript, text/javascript, text/ecmascript]
extension : [js]
可以看到对js的支持,下面入门一个示例demo展示如何使用该api:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/**
* 腳本语言
* @Author xingo
* @Date 2023/11/10
*/
public class JavaScriptDemo {
public static void main(String[] args) throws ScriptException {
// 1、创建一个脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
// 2、从引擎管理器中获取一个引擎可以有下面三种方式:
// getEngineByExtension()、getEngineByMimeType()、getEngineByName()。
// 只要参数名能对上就可以,参数名称就是上面代码执行输出展示的内容
// ScriptEngine engine = manager.getEngineByExtension("js");
// ScriptEngine engine = manager.getEngineByMimeType("text/javascript");
ScriptEngine engine = manager.getEngineByName("JavaScript");
// 3、执行js脚本,调用eval()方法
String script = "var a = 1, b = 2;\n" +
"var c = a + b;\n" +
"print(c);";
engine.eval(script);
}
}
上面的这种方式,参数已经在脚本中定义好了,但是在项目中,参数一般是通过方法调用时传递进去的,这时候就需要有方法将参数设置,脚本引擎是支持的,通过 put()
方法将参数传入:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/**
* 腳本语言
* @Author xingo
* @Date 2023/11/10
*/
public class JavaScriptDemo {
public static void main(String[] args) throws ScriptException {
// 1、创建一个脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
// 2、获取引擎
ScriptEngine engine = manager.getEngineByName("js");
// 3、设置参数
engine.put("a", 1);
engine.put("b", 2);
// 4、执行脚本并获取结果输出
String script = "function add(a, b) {\n" +
"return a + b;\n" +
"}\n" +
"add(a, b);";
Object val = engine.eval(script);
System.out.println("sum : " + val);
}
}
对于上面put的变量,它作用于自身engine范围内,也就是ScriptContext.ENGINE_SCOPE,put 的变量放到一个叫Bindings的Map中,可以通过 engine.getBindings(ScriptContext.ENGINE_SCOPE).get(“a”);得到put的内容。和 ENGINE_SCOPE相对,还有个ScriptContext.GLOBAL_SCOPE 作用域,其作用的变量是由同一ScriptEngineFactory创建的所有ScriptEngine共享的全局作用域。也可以将上面的代码调整为如下形式:
import javax.script.*;
/**
* 腳本语言
* @Author xingo
* @Date 2023/11/10
*/
public class JavaScriptDemo {
public static void main(String[] args) throws ScriptException {
// 1、创建一个脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
// 2、获取引擎
ScriptEngine engine = manager.getEngineByName("js");
// 3、设置参数:通过Bindings设置
Bindings bind = engine.createBindings();
bind.put("a", 1);
bind.put("b", 2);
// 绑定上下文,作用域为当前引擎范围
engine.setBindings(bind, ScriptContext.ENGINE_SCOPE);
// 4、执行脚本并获取结果输出
String script = "function add(a, b) {\n" +
"return a + b;\n" +
"}\n" +
"add(a, b);";
Object val = engine.eval(script);
System.out.println("sum : " + val);
}
}
在项目开发过程中一般都是定义好js脚本,然后在需要运行的时候才执行对应的方法,这时候用上面的方式就不行了,需要使用Invocable的invokeFunction()方法实现定义的js函数调用,Invocable可以多次调用脚本库中的函数,使用示例如下:
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
/**
* 腳本语言
* @Author xingo
* @Date 2023/11/10
*/
public class JavaScriptDemo {
public static void main(String[] args) throws Exception {
// 1、创建一个脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
// 2、获取引擎
ScriptEngine engine = manager.getEngineByName("js");
// 3、加载需要运行的脚本代码
String script = "function add(a, b) {\n" +
"return a + b;\n" +
"}";
engine.eval(script);
// 4、动态调用函数并获取结果
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("add", 1, 2);
System.out.println("sum : " + result);
}
}
Invocable不但可以调用js中定义的方法,还可以调用java中的接口,在接口中定义方法名,方法的实现使用js代码开发,只要保证接口中的方法名和js的方法名一致就可以调用了。
比如我定义了一个java接口如下:
/**
* 定义一个接口,接口的实现在js中
* @Author wangxixin
* @Date 2023/11/10
*/
public interface JavaScriptMethod {
/**
* 求两个数的和
* @param a 参数1
* @param b 参数2
* @return
*/
int add(int a, int b);
/**
* 求两个数的最大值
* @param a 参数1
* @param b 参数2
* @return
*/
int max(int a, int b);
}
这时我要调用方法就可以改成下面这种方式:
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
/**
* 腳本语言
* @Author xingo
* @Date 2023/11/10
*/
public class JavaScriptDemo {
public static void main(String[] args) throws Exception {
// 1、创建一个脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
// 2、获取引擎
ScriptEngine engine = manager.getEngineByName("js");
// 3、加载需要运行的脚本代码
String script = "function add(a, b) {\n" +
"return a + b;\n" +
"}\n" +
"function max(a, b) {\n" +
"return a >= b ? a : b;\n" +
"}";
engine.eval(script);
// 4、动态调用函数并获取结果
Invocable invocable = (Invocable) engine;
JavaScriptMethod jsMethod = invocable.getInterface(JavaScriptMethod.class);
System.out.println("sum : " + jsMethod.add(1, 2));
System.out.println("max : " + jsMethod.max(1, 2));
}
}
脚本引擎不但可以在java代码中调用js开发的脚本代码,也可以将java中的对象交给脚本执行,但js是解释型语言,跟java这种编译型语言的执行性能还是有很大差距的,作为知识点还是要了解一下:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
/**
* 腳本语言
* @Author xingo
* @Date 2023/11/10
*/
public class JavaScriptDemo {
public static void main(String[] args) throws Exception {
// 1、创建一个脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
// 2、获取引擎
ScriptEngine engine = manager.getEngineByName("js");
// 3、使用java中的对象
String[] arr = { "kafka", "RocketMQ", "RabbitMQ" };
engine.put("arr", arr);
String script = "for(var i = 0; i < arr.length; i++) {\n" +
"print(arr[i]);\n" +
"}";
engine.eval(script);
}
}
脚本引擎默认是解释执行的,如果需要反复执行脚本,可以使用它的可选接口Compilable来编译执行脚本,以获得更好的性能:
import javax.script.*;
/**
* 腳本语言
* @Author xingo
* @Date 2023/11/10
*/
public class JavaScriptDemo {
public static void main(String[] args) throws Exception {
// 1、创建一个脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
// 2、获取引擎
ScriptEngine engine = manager.getEngineByName("js");
// 3、编译脚本代码
Compilable compilable = (Compilable) engine;
String script = "function add(a, b) {\n" +
"return a + b;\n" +
"}";
CompiledScript compiledScript = compilable.compile(script);
compiledScript.eval();
// 4、调用方法获取执行结果
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("add", 1, 2);
System.out.println("sum : " + result);
}
}
上面的示例程序都是将js脚本代码嵌入到java代码中,如果要修改js代码就必须要重新打包发布项目,其实我们可以将js代码写入文件中,在代码中加载该文件,这时如果要修改js代码就不用重新编译java代码:
比如在我的E盘下有一个demo.js文件:
function add(a, b) {
return a + b;
}
在java代码中加载该js文件并调用js中的方法:
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.FileReader;
/**
* 腳本语言
* @Author xingo
* @Date 2023/11/10
*/
public class JavaScriptDemo {
public static void main(String[] args) throws Exception {
// 1、创建一个脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
// 2、获取引擎
ScriptEngine engine = manager.getEngineByName("js");
// 3、加载js文件
engine.eval(new FileReader("E:\\demo.js"));
// 4、调用方法获取执行结果
Invocable invocable = (Invocable) engine;
Object result = invocable.invokeFunction("add", 1, 2);
System.out.println("sum : " + result);
}
}
以上总结的就是在java中脚本引擎的常用API,使用场景其实并不多。我在做项目过程中就有一个需求用到过:用户想自定义一些计算公式,这些计算公式也不复杂,都是一些加减乘除运算,在写这个需求时我就把用户的公式保存下来,然后在调用这些公式时使用了脚本引擎,用很少的代码就完成了这个功能,还是很好用的。