Java直接运⾏JavaScript代码
⾃JDK1.6开始,已经⾃带了⼀个ScriptEngine,可以⽤来执⾏如javascript何groovy脚本代码。在实际场景中基本上都是在多线程环境下使⽤的,⽐如在servlet中执⾏⼀个脚本对推荐结果列表做⼆次转换后再返回给前端结果。
可以通过执⾏⼀下代码可以查看你当前使⽤的jdk在⽀持的脚本的线程安全性:
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
public class ScriptEngineTest {
public static void main(String[] args) {
final ScriptEngineManager mgr = new ScriptEngineManager();
for(ScriptEngineFactory fac: EngineFactories()) {
System.out.println(String.format("%s (%s), %s (%s), %s", EngineName(),
}
}
}
o, our agenda(议事⽇程) is two fold. The first is to provide a "workers" library (timeline is not tied to JDK8) which uses an onevent model
that JavaScripters are familiar with. No synchronization/locking constructs to be added to the language. Communication between threads
(and potentially nodes/servers) is done using JSON (under the covers.) Going this route allows object isolation between threads, but also allows
maximal use of CPU capacity.
The second part is, we will not guarantee MT-safe structures, but we have to face reality. Developers will naturally be drawn to using threads.
Many of the Java APIs require use of threads. So, the best we can do is provide guidelines on how to not shoot yourself in the foot(搬起⽯头砸⾃⼰的脚
These guidelines will evolve(发展) and we'll post them 'somewhere' after we think them through. In the meantime, I follow some basic rules;
Avoid sharing script objects or script arrays across threads (this includes global.) Sharing script objects is asking for trouble. Share only
primitive data types, or Java objects.
If you want to pass objects or arrays across threads, use JSON.stringify(obj) andJSON.parse(string) to transport using strings.
If you really really feel you have to pass a script object, treat the object as a constant and only pass the object to new threads (coherency.)
Consider using Object.freeze(obj).
If you really really really feel you have to share a script object, make sure the object's properties are stabilized. No adding or removing
properties after sharing starts. Consider using Object.seal(obj).
Given enough time, any other use of a shared script object will eventually cause your app to fail.
⾥⾯说到了⼏点值得注意的,就是不要共享脚本中的对象,可以共享原⽣数据类型或者java对象,⽐如你在script⾥⾯定义⼀个var i=0,那么这个变量i是全局的变量,是所有线程共享的变量,当你在多线程情况下执⾏var i=0; i=i+1;时,每个线程得到的结果并不会都是1。当然如果你是在script的function中定义的变量,那么它不会被共享,例如你的script string是:function addone(){var i=0;i=i+1;return i;}那么多线程同时调⽤这个function时,返回的结果都是1。
这⾥要注意的⼀点是function中⼀定要⽤var 重新定义变量,否则还是全局的变量.
如果你确实想共享⼀个对象,可以⽤JSON.stringfy和JSON.parse通过json序列化和反序列化来共享,这样其实内部是⽣成java 的String 对象,String对象都是新分配内存保存的,所以每个线程都持有的是不同的对象实例,改变互不影响。
尽量复⽤ScriptEngine,因为是将脚本语⾔编译成了java可执⾏的字节码来执⾏的,如果不复⽤的话,
那么每次都要去编译脚本⽣成字节码,如果是⼀个固定的脚本的话,这样效率是很低的
如何去在servlet中复⽤ScriptEngine:
public class MyServlet extends HttpServlet {
private static ThreadLocal<ScriptEngine> engineHolder;
@Override
public void init() throws ServletException {
engineHolder = new ThreadLocal<ScriptEngine>() {
@Override
protected ScriptEngine initialValue() {
return new ScriptEngineManager().getEngineByName("JavaScript");
}
};
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
try (PrintWriter writer = Writer()) {
ScriptContext newContext = new SimpleScriptContext();
newContext.().createBindings(), ScriptContext.ENGINE_SCOPE);
Bindings engineScope = Bindings(ScriptContext.ENGINE_SCOPE);
engineScope.put("writer", writer);
Object value = ().eval("writer.print('Hello, World!');", engineScope);
writer.close();
} catch (IOException | ScriptException ex) {
}
}
javascript全局数组}
使⽤⼀个ThreadLocal来共享ScriptEngine,这样在后来的请求可以复⽤这个ScriptEngine实例。即每个线程都有⾃⼰的ScriptEngine实例。由于线程池的存在,线程可以被复⽤,从⽽ThreadLocal⾥的ScriptEngine也被复⽤了。
给⼀个测试多线程使⽤的例⼦:
import jdk.nashorn.api.scripting.NashornScriptEngine;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import urrent.*;
public class JavaExecScriptMTDemo {
public static void main(String[] args) throws Exception {
ScriptEngineManager sem = new ScriptEngineManager();
NashornScriptEngine engine = (NashornScriptEngine) EngineByName("javascript");
String script = "function transform(arr){" +
" var arr2=[]; for(var i=0;i<arr.size();i++){arr2[i]=arr[i]+1;} verse(); " + "}";
engine.eval(script);
Callable<Collection> addition = new Callable<Collection>() {
@Override
public Collection call() {
try {
ScriptObjectMirror mirror= (ScriptObjectMirror)engine.invokeFunction("transform", Arrays.asList(1, 2, 3));
return mirror.values();
} catch (ScriptException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
};
ExecutorService executor = wCachedThreadPool();
ArrayList<Future<Collection>> results = new ArrayList<>();
for (int i = 0; i < 50; i++) {
results.add(executor.submit(addition));
}
int miscalculations = 0;
for (Future<Collection> result : results) {
Collection jsResult = ();
System.out.println(jsResult);
}
executor.awaitTermination(1, TimeUnit.SECONDS);
executor.shutdownNow();
}
脚本内容是⼀个函数,传⼊⼀个整型数组,然后将数组中的每个元素+1,返回⼀个新的数组。启动50个线程同时执⾏这个函数,会发现输出的结果都是[2,3,4]。说明这种⽅式的⽤法是线程安全的,因为我们没有共享脚本变量。
当我们改变⼀下脚本内容,改成这样:
String script = "function transform(arr){" +
" var arr2=[]; for( i=0;i<arr.size();i++){arr2[i]=arr[i]+1;} verse(); " +
"}";
就是把for循环上的var i=0改成了i=0.然后再执⾏测试代码。会发现抛出数组越界异常。说明这种情况下多线程不安全了,因为这个i是⼀个共享脚本变量,每个脚本下都可见。
script包下最主要的是ScriptEngineManager、ScriptEngine、CompiledScript和Bindings 4个类或接⼝。
ScriptEngineManager是⼀个⼯⼚的集合,可以通过name或tag的⽅式获取某个脚本的⼯⼚,并⽣成⼀个此脚本的ScriptEngine,ScriptEngine engine=new ScriptEngineManager().getEngineByName("JavaScript");
通过⼯⼚函数得到了ScriptEngine之后,就可以⽤这个对象来解析脚本字符串,直接调⽤Object obj = ScriptEngine.eval(String script)即可,返回的obj为表达式的值,⽐如true、false或int值。
ScriptEngine:是⼀个脚本引擎,包含⼀些操作⽅法,eval,createBindings,setBindings
engine.eval(option); //option:"10+((D-parseInt(D/28)*28)/7+1)*10";
option 可以是⼀段js代码,函数,函数传参需要调⽤ ateBindings获得bindings,bindings.put(key,value)来传⼊参数
CompiledScript可以将ScriptEngine解析⼀段脚本的结果存起来,⽅便多次调⽤。只要将ScriptEngine⽤Compilable接⼝强制转换后,调⽤compile(String script)就返回了⼀个CompiledScript对象,要⽤的时候每次调⽤⼀下CompiledScript.eval()即可,⼀般适合⽤于js函数的使⽤。
Compilable compilable=(Compilable) engine;
CompiledScript JSFunction=compilablepile(option); //解析编译脚本函数
Bindings ateBindings();
bindings.put(key,value);
JSFunction.eval(bingdings);
Bindings有3个层级,为Global级、Engine级和Local级,前2者通过Bindings()获得,是唯⼀的对象,⽽Local Binding 由ateBindings()获得,很好理解,每次都产⽣⼀个新的。Global对应到⼯⼚,Engine对应到ScriptEngine,向这2者⾥⾯加⼊任何数据或者编译后的脚本执⾏对象,在每⼀份新⽣成的Local Binding⾥⾯都会存在。
String option="function getNum(num){if(num%7==0){ return 5+5*(parseInt(5*(num-parseInt(num/29)*28)/28)+1)}else{return 5;}} getNum(num)"
-------------------------------
try{
Compilable compilable = (Compilable) engine;
Bindings bindings = ateBindings(); //Local级别的Binding
CompiledScript JSFunction = compilablepile(option); //解析编译脚本函数
for(Map.Entry<String,Object> Set()){
bindings.Key(),Value());
}
Object result=JSFunction.eval(bindings);
System.out.println(result);
return (int)Double.String());
}catch (ScriptException e) {
e.printStackTrace();
}
Script Variables 脚本变量
当您使⽤Java应⽤程序嵌⼊脚本引擎和脚本时,,可能希望将应⽤程序对象做为脚本的全局变量。此⽰例演⽰如何将应⽤程序对象公开为脚本的全局变量。我们在应⽤中创建了⼀个java.io.File对象,使⽤名称“file”显⽰与全局变量相同的内容。这个脚本可以读取这个变量。例如:它可以调⽤这个变量的权限修饰为public的⽅法。请注意,访问Java对象,⽅法和字段的语法取决于脚本语⾔。 JavaScript⽀持最“⾃然”的类似Java的语法。
public class ScriptVars {
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = EngineByName("Nashorn");
File f = new File("");
//将File对象f直接注⼊到js脚本中并可以作为全局变量使⽤
engine.put("file", f);
// evaluate a script string. The script accesses "file"
// variable and calls method on it
engine.eval("AbsolutePath())");
}
}
Invoking Script Functions and Methods java中执⾏js函数
有时你需要重复调⽤⼀个特定的脚本函数,例如,您的应⽤程序菜单功能可能由脚本实现。在你的菜单的事件处理器中你可能想调⽤⼀个脚本的函数。以下⽰例演⽰了从Java代码调⽤特定的脚本函数。
import javax.script.*;
public class InvokeScriptFunction {
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = EngineByName("Nashorn");
// String定义⼀个js函数
String script = "function hello(name) { print('Hello, ' + name); }";
// 直接执⾏上⾯的js函数(这个函数是全局的,在下⾯的js引擎中依然可以调⽤,不会执⾏完就消失)
engine.eval(script);
// javax.script.Invocable 是⼀个可选的接⼝
// 检查脚本引擎是否被实现!
// 注意:JavaScript engine 实现了 Invocable 接⼝
Invocable inv = (Invocable) engine;
// 执⾏这个名字为 "hello"的全局的函数
inv.invokeFunction("hello", "Scripting!!" );
}
}
如果您的脚本语⾔是基于对象的(如JavaScript)或⾯向对象的,则你可以在脚本对象上调⽤脚本⽅法。import javax.script.*;
public class InvokeScriptMethod {
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = EngineByName("JavaScript");
// ⽤String定义了⼀段JavaScript代码这段代码定义了⼀个对象'obj'
// 给对象增加了⼀个名为 hello 的⽅法(hello这个⽅法是属于对象的)
String script = "var obj = new Object(); obj.hello = function(name) { print('Hello, ' + name); }";
//执⾏这段script脚本
engine.eval(script);
// javax.script.Invocable 是⼀个可选的接⼝
// 检查你的script engine 接⼝是否已实现!
// 注意:JavaScript engine实现了Invocable接⼝
Invocable inv = (Invocable) engine;
// 获取我们想调⽤那个⽅法所属的js对象
Object obj = ("obj");
// 执⾏obj对象的名为hello的⽅法
inv.invokeMethod(obj, "hello", "Script Method !!" );
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论