概述
一、java的脚本执行引擎ScriptEngine
从JDK6开始,java就嵌入了对脚本的支持,这里的脚本指的是但非局限于JS这样的非java语言,当时使用的脚本执行引擎是基于Mozilla 的Rhino。该引擎的特性允许开发人员将 JavaScript 代码嵌入到 Java 中,甚至从嵌入的 JavaScript 中调用 Java。此外,它还提供了使用jrunscript
从命令行运行 JavaScript 的能力。
Java ScriptEngine优缺点:
优点:可以执行完整的JS方法,并且获取返回值;在虚拟的Context中执行,无法调用系统操作和IO操作,非常安全;可以有多种优化方式,可以预编译,编译后可以复用,效率接近原生Java;所有实现ScriptEngine接口的语言都可以使用,并不仅限于JS,如Groovy,Ruby等语言都可以动态执行。
缺点:无法调用系统和IO操作 ,也不能使用相关js库,只能使用js的标准语法。更新:可以使用scriptengine.put()将Java原生Object传入Context,从而拓展实现调用系统和IO等操作。
二、Nashorn JavaScript 引擎
从JDK 8开始,Nashorn取代Rhino成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的invokedynamic,将JavaScript编译成Java字节码。
nashorn首先编译javascript代码为java字节码,然后运行在jvm上,底层也是使用invokedynamic命令来执行,所以运行速度很给力。
Nashorn是一个纯编译的JavaScript引擎。它没有用Java实现的JavaScript解释器,而只有把JavaScript编译为Java字节码再交由JVM执行这一种流程,跟Rhino的编译流程类似。
[ JavaScript源码 ] -> ( 语法分析器 Parser ) -> [ 抽象语法树(AST) ir ] -> ( 编译优化 Compiler ) -> [ 优化后的AST + Java Class文件(包含Java字节码) ] -> JVM加载和执行生成的字节码 -> [ 运行结果 ]
只从JVM以上的层面看,Nashorn是一种单层的纯编译型JavaScript实现。所有JavaScript代码在首次实际执行前都会被编译为Java字节码交由JVM执行。(当然JVM自身可能是混合执行模式的,例如HotSpot VM与J9 VM。所以Nashorn在实际运行中可能需要一定预热才会达到最高速度)
1. java代码中使用 nashorn
为了在java中执行JavaScript代码,首先使用原先Rhino (旧版Java1.6中来自Mozilla的引擎)中的包javax.script来创建一个nashorn脚本引擎。
把JavaScript代码作为一个字符串来直接执行,也可放入一个js脚本文件中
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello World!');");
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));
2. 向Java传递数据或者从Java传出数据
可以将数据作为字符串显式传递
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
String name = "Olli";
nashorn.eval("print('" + name + "')");
可以在Java中传递绑定,它们是可以从JavaScript引擎内部访问的全局变量
int valueIn = 10;
SimpleBindings simpleBindings = new SimpleBindings();
simpleBindings.put("globalValue", valueIn);
nashorn.eval("print (globalValue)", simpleBindings);
JavaScript eval的求值结果将会从引擎的“eval”方法返回
Integer result = (Integer) nashorn.eval("1 + 2");
assert(result == 3);
2.1 在 JavaScript 端调用 Java 方法
在 JavaScript 中调用 Java 方法很简单。首先我们定义一个静态的 Java 方法:
static String fun1(String name) {
System.out.format("Hi there from Java, %s", name);
return "greetings from java";
}
JavaScript 可通过 Java.type API 来引用 Java 类。这跟在 Java 类中引入其他类是类似的。当定义了 Java 类型后我们可直接调用其静态方法 fun1() 并打印结果到 sout。因为方法是静态的,所以我们无需创建类实例。
var MyJavaClass = Java.type('my.package.MyJavaClass');
var result = MyJavaClass.fun1('John Doe');
print(result);
三、nashorn的sandbox的使用
java提供脚本支持,这给我们业务提供了便利的同时,也给我们的服务带来了更大的风险,因为如果我们的业务需求是提供执行脚本的接口,那么JS脚本就是由客户端输入,这存在很多的不确定性,存在安全隐患,比如:
- js代码存在死循环
- js代码可以操作宿主机上面的功能,删除机器上的文件
- js执行占用过多的java资源
这个时候sandbox就应运而生了,sandbox的作用就是将JS脚本执行的环境独立出来,达到对java类的访问限制以及对Nashorn引擎的资源限制的目的。
1、创建sandbox
导包:
<dependency>
<groupId>org.javadelight</groupId>
<artifactId>delight-nashorn-sandbox</artifactId>
<version>0.2.5</version>
</dependency>
创建sandbox:
NashornSandbox sandbox = NashornSandboxes.create();
配置sandbox:
sandbox.setMaxCPUTime(100);// 设置脚本执行允许的最大CPU时间(以毫秒为单位),超过则会报异常,防止死循环脚本
sandbox.setMaxMemory(1024 * 1024); //设置JS执行程序线程可以分配的最大内存(以字节为单位),超过会报ScriptMemoryAbuseException错误
sandbox.allowNoBraces(false); // 是否允许使用大括号
sandbox.allowLoadFunctions(true); // 是否允许nashorn加载全局函数
sandbox.setMaxPreparedStatements(30); // because preparing scripts for execution is expensive // LRU初缓存的初始化大小,默认为0
sandbox.setExecutor(Executors.newSingleThreadExecutor());// 指定执行程序服务,该服务用于在CPU时间运行脚本
2、sandbox的使用
eval方法:
在sandbox中,使用eval可以直接编译执行JS脚本,但是,如果我们的JS脚本是一个完整的function方法,那么eval方法会编译该方法但是并不会去执行:
可以看到,我们的JS方法中有一个return ‘Hello Word !’的代码,正常执行的话,应该是有一个“Hello Word”的返回值的,但是我们看输出结果,返回值为true,说明我们的JS代码语法没有问题,被正常的编译进入sandbox中,但是我们并没有去调用执行该方法,那么我们把脚本代码稍微修改一下:
我们在方法的后面跟了一个test()
,这相当于我们对test方法进行了调用,所以这次我们的返回值是我们预想的结果:Hello Word!
-
直接使用eval执行无参数JS代码
sandbox.eval("1 + 1");
-
使用eval执行有参数的JS代码
Bindings bindings = sandbox.createBindings(); bindings.put("a", 1); bindings.put("b", 2); bindings.put("c", 2); sandbox.eval("function calFunction(a, b, c){return a+b+c;}", bindings);
-
使用compile先编译无参脚本,再使用eval执行
刚才上面有说到nashorn执行JS的原理,当我们传入JS脚本时,nashorn会将JS脚本编译成JVM能够识别的字节码文件,然后交由JVM去进行执行。
[在这插一句:如果大家有精力的话可以去了解一下JVM的动态编译,像我们平时使用IDEA启动JAVA服务,都是现将JAVA项目文件编译为.class文件以后再去执行,这就是所谓的静态编译。动态编译是指服务在运行的过程中,实时编译代码交由JVM进行执行(大致是这么个意思,详情目前了解也不是很透彻)。而nashorn的原理就跟java的动态编译很类似,是在服务运行过程中去编译文件进行执行]
而编译是比较消耗资源和时间的,所以nashorn中也提供了编译的机制,一次编译多次执行。
CompiledScript compile = sandbox.compile("111+111"); compile.eval()
-
使用compile先编译有参脚本,再使用eval执行
Bindings bindings = sandbox.createBindings(); bindings.put("a", 1); bindings.put("b", 2); bindings.put("c", 2); CompiledScript compile = sandbox.compile("function calFunction(a, b, c){return a+b+c;};calFunction(a,b,c)"); compile.eval(bindings)
3、sandbox进阶使用
-
在JS脚本中使用java中的类
刚才我们有说到,sandbox限制了我们对java类的引用,但是sandbox又很人性化的给我们留了后门,在配置sandbox时,我们可以使用
allow()
方法来允许sandbox可以使用java的某个类。
sandbox.allow(StringBuffer.class)
在未使用 allow之前:
在使用之后:
在这个例子中,我们在JS中是使用Java.type()
方法来得到Java类的对象并使用。需要注意的是,如果想在JS中使用java类的方法,前提是java类中的方法必须是静态的。
这是githup中,nashornSandBox项目的对JS执行JAVA对象的解释
- 在方法中引用别的方法
JS执行引擎的局限性还是蛮大的,总体感觉就是一个加工厂,往工厂中加入原料(传入JS脚本),然后加工厂对原料进行加工(编译),最后生成产品返回(执行并返回值)。如果说,我们在JS脚本中使用JS的语法规范引入别的包或者方法,那么JS执行引擎就傻嘚了,他不知道该去哪里找这个包或者说这个方法,那么执行就会报错。
当然,有问题那么就有解决办法。在nashorn中,JS脚本是允许预编译的,一次编译多次使用。按照这个思路,我们完全可以先把方法要引用的其他方法先一步编译到JS执行引擎中,这样在执行方法时就能直接找到预编译好的方法进行调用,那么我们来改一下上面这个例子:
同样的test方法,不过我先一步将他要使用的test2方法编译到执行器中,这样就能执行成功了
但是,需要注意的是,在JS调用别的方法,方法名称是作为唯一标识的,如果往执行器中编译两个同名的方法,后者的方法会覆盖前者的方法
- 预编译方法的调用
在上面的例子中我们看到,JS脚本是可以提前预编译到JS执行器中,后续方法可以直接调用。但是,在上面的所有例子中,我们执行方法都是使用的eval方法,并且在JS脚本中去做调用:
这种方式是不合理的,如果我们只是想编译,并不想执行,那么这样就会去做多余操作。但是如果不加这个,我们后续又该怎么去执行已经编译在JS引擎中的方法呢?
在这里sandbox提供了.getSandboxedInvocable().invokeFunction()
方法,这个方法可以直接通过方法名称去执行已经编译在执行器中的方法:
能读完也是蛮厉害的,给你点个赞!!!!
本文属于自学笔记,如果内容中有什么不对的地方,欢迎各位留言吐槽!!
最后
以上就是迷人小刺猬为你收集整理的nashorn 和 delight-nashorn-sandbox 学习笔记一、java的脚本执行引擎ScriptEngine二、Nashorn JavaScript 引擎三、nashorn的sandbox的使用的全部内容,希望文章能够帮你解决nashorn 和 delight-nashorn-sandbox 学习笔记一、java的脚本执行引擎ScriptEngine二、Nashorn JavaScript 引擎三、nashorn的sandbox的使用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复