目录

  • 一、什么是脚本引擎
  • 1. Java ScriptEngine优缺点
  • 二、Nashorn JavaScript 引擎
  • 三、如何使用Nashorn
  • 1. java代码中使用 nashorn
  • 2. 向Java传递数据或者从Java传出数据
  • 2.1 在 JavaScript 端调用 Java 方法
  • 四、常用类总结
  • 1. ScriptEngine、ScriptEngineManager
  • 2. CompiledScript
  • 3. Bindings
  • 4. ScriptContext
  • 五、参考


一、什么是脚本引擎

使得Java应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在Java平台上调用各种脚本语言的目的。

Java脚本API是连通Java平台和脚本语言的桥梁。

可以把一些复杂易变的业务逻辑交给脚本语言处理,这又大大提高了开发效率。

1. Java ScriptEngine优缺点

优点:可以执行完整的JS方法,并且获取返回值;在虚拟的Context中执行,无法调用系统操作和IO操作,非常安全;可以有多种优化方式,可以预编译,编译后可以复用,效率接近原生Java;所有实现ScriptEngine接口的语言都可以使用,并不仅限于JS,如Groovy,Ruby等语言都可以动态执行。

缺点:无法调用系统和IO操作 ,也不能使用相关js库,只能使用js的标准语法。更新:可以使用scriptengine.put()将Java原生Object传入Context,从而拓展实现调用系统和IO等操作。

二、Nashorn JavaScript 引擎

Nashorn,发音“nass-horn”,是德国二战时一个坦克的命名,同时也是java8新一代的javascript引擎–替代老旧,缓慢的Rhino,符合 ECMAScript-262 5.1 版语言规范。

javascript运行在jvm已经不是新鲜事了,Rhino早在jdk6的时候已经存在,但现在为何要替代Rhino,官方的解释是Rhino相比其他javascript引擎(比如google的V8)实在太慢了,要改造Rhino还不如重写。因此有了Nashorn。

nashorn首先编译javascript代码为java字节码,然后运行在jvm上,底层也是使用invokedynamic命令来执行,所以运行速度很给力。

从JDK 8开始,Nashorn取代Rhino成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的invokedynamic,将JavaScript编译成Java字节码。

官方参考URL: https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/

三、如何使用Nashorn

Nashorn javascript 引擎要么在java程序中以编程的方式使用要么在命令行工具jjs使用,jjs在目录$JAVA_HOME/bin中。如果你准备建立一个jjs的符号链接,如下:

$ cd /usr/bin
$ ln -s $JAVA_HOME/bin/jjs jjs
$ jjs
jjs> print('Hello World');

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传出数据

  • 可以将数据作为字符串显式传递
    如下的 name变量
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);

四、常用类总结

1. ScriptEngine、ScriptEngineManager

代码注释如下:

/**
 * <code>ScriptEngine</code> is the fundamental interface whose methods must be
 * fully functional in every implementation of this specification.
 * <br><br>
 * These methods provide basic scripting functionality.  Applications written to this
 * simple interface are expected to work with minimal modifications in every implementation.
 * It includes methods that execute scripts, and ones that set and get values.
 * <br><br>
 * The values are key/value pairs of two types.  The first type of pairs consists of
 * those whose keys are reserved and defined in this specification or  by individual
 * implementations.  The values in the pairs with reserved keys have specified meanings.
 * <br><br>
 * The other type of pairs consists of those that create Java language Bindings, the values are
 * usually represented in scripts by the corresponding keys or by decorated forms of them.
 *
 * @author Mike Grogan
 * @since 1.6
 */

public interface ScriptEngine  {

ScriptEngine 是个接口类,它规范了包括执行脚本的方法,以及set和get值的方法。

demo:

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
 
public class JSTesterCallFunctions {
    public static void main(String[] args){
	ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
	ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
	try{
		for(int i = 0; i < args.length; i++){
			File file = new File(args[i]).getAbsoluteFile();
			System.out.println("args[" +i+ "] is" + file);
			if(file.isFile()){
				String strJSName = args[i];// For Jar
				nashorn.eval(new FileReader(strJSName ));
				Object eval = nashorn.eval("make('" + strJSName + "')");// 传递参数strJSName到JS脚本
			else{
				System.out.println("Java运行命令参数非法,请检查!");
			}
		}
	} catch (ScriptException | FileNotFoundException e) {
		System.out.println("Error executing script: " + e.getMessage());
	}
    }
}

ScriptEngineManager是ScriptEngine的工厂,实例化该工厂的时候会加载可用的所有脚本引擎。从工厂中创建ScriptEngine可以使用getEngineByName、getEngineByExtension或 getEngineByMimeType来得到,只要参数名字能对上。执行脚本调用eval方法即可(效果等同于javascript中的eval)。

2. CompiledScript

把脚本编译成java字节码。可以让脚本重复执行,无需重新解析脚本。每个compiledScript都与一个scripteEngine——一个对一个eval的调用相关联。

/**
 * Extended by classes that store results of compilations.  State
 * might be stored in the form of Java classes, Java class files or scripting
 * language opcodes.  The script may be executed repeatedly
 * without reparsing.
 * <br><br>
 * Each <code>CompiledScript</code> is associated with a <code>ScriptEngine</code> -- A call to an  <code>eval</code>
 * method of the <code>CompiledScript</code> causes the execution of the script by the
 * <code>ScriptEngine</code>.  Changes in the state of the <code>ScriptEngine</code> caused by execution
 * of the <code>CompiledScript</code>  may visible during subsequent executions of scripts by the engine.
 *
 * @author Mike Grogan
 * @since 1.6
 */
public abstract class CompiledScript {

使用如下:

ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine scriptEngine = manager.getEngineByName( "javascript" );

        Bindings scope = scriptEngine.getBindings( ScriptContext.ENGINE_SCOPE );
        scope.put( "TransformationName", "xxx" );
        CompiledScript startScript = ( (Compilable) scriptEngine ).compile( strStartScript );

        startScript.eval( scope );

3. Bindings

如下源码,Bindings就是一个继承Map接口的接口,所有key都是字符串。

/**
 * A mapping of key/value pairs, all of whose keys are
 * <code>Strings</code>.
 *
 * @author Mike Grogan
 * @since 1.6
 */
public interface Bindings extends Map<String, Object> {
ScriptEngine 有一个getBindings方法,返回Bindings 类型。该方法:

返回命名值的作用域。
可能的范围是:

  • scriptcontext.global_scope:表示全局的命名值集范围
  • scriptcontext.engine_scope: 表示状态的命名值集是scripteEngine
/**
     * Returns a scope of named values.  The possible scopes are:
     * <br><br>
     * <ul>
     * <li><code>ScriptContext.GLOBAL_SCOPE</code> - The set of named values representing global
     * scope. If this <code>ScriptEngine</code> is created by a <code>ScriptEngineManager</code>,
     * then the manager sets global scope bindings. This may be <code>null</code> if no global
     * scope is associated with this <code>ScriptEngine</code></li>
     * <li><code>ScriptContext.ENGINE_SCOPE</code> - The set of named values representing the state of
     * this <code>ScriptEngine</code>.  The values are generally visible in scripts using
     * the associated keys as variable names.</li>
     * <li>Any other value of scope defined in the default <code>ScriptContext</code> of the <code>ScriptEngine</code>.
     * </li>
     * </ul>
     * <br><br>
     * The <code>Bindings</code> instances that are returned must be identical to those returned by the
     * <code>getBindings</code> method of <code>ScriptContext</code> called with corresponding arguments on
     * the default <code>ScriptContext</code> of the <code>ScriptEngine</code>.
     *
     * @param scope Either <code>ScriptContext.ENGINE_SCOPE</code> or <code>ScriptContext.GLOBAL_SCOPE</code>
     * which specifies the <code>Bindings</code> to return.  Implementations of <code>ScriptContext</code>
     * may define additional scopes.  If the default <code>ScriptContext</code> of the <code>ScriptEngine</code>
     * defines additional scopes, any of them can be passed to get the corresponding <code>Bindings</code>.
     *
     * @return The <code>Bindings</code> with the specified scope.
     *
     * @throws IllegalArgumentException if specified scope is invalid
     *
     */
    public Bindings getBindings(int scope);

语言绑定对象实际上就是一个简单的哈希表,用来存放和获取需要共享的数据,其定义的接口为javax.script.Bindings,继承自java.util.Map接口。一个脚本引擎在执行过程中可能会使用多个语言绑定对象,不同语言绑定对象的作用域不同。ScriptEngine类提供out和get方法对脚本引擎中特定作用域的默认语言绑定对象进行操作。

使用默认的语言绑定对象:

public void useDefaultBinding() throws ScriptException {  
    ScriptEngine engine = getJavaScriptEngine();  
    engine.put("name", "Alex");  
    engine.eval("var message = 'Hello, ' + name;");  
    engine.eval("println(message);");  
    Object obj = engine.get("message");  
    System.out.println(obj);  
}

可以自定义语言绑定对象(如语言绑定对象中包含程序自己独有的数据等情形……):

public void useCustomBinding() throws ScriptException {  
    ScriptEngine engine = getJavaScriptEngine();  
    Bindings bindings = new SimpleBindings();  
    bindings.put("hobby", "playing games");  
    engine.eval("println('I like ' + hobby);", bindings);  
}

4. ScriptContext

使用如下:

ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine scriptEngine = manager.getEngineByName( "javascript" );

        Bindings scope = scriptEngine.getBindings( ScriptContext.ENGINE_SCOPE );
        scope.put( "TransformationName", "xxx" );

ScriptContext源码注释如下:
该接口的实现类用于连接脚本引擎
在宿主应用程序中使用对象(如作用域绑定)。每个作用域都是一组
其值可以使用。scriptcontex.脚本上下文暴露读和写,即用于脚本引擎的输入和输出。

/**
 * The interface whose implementing classes are used to connect Script Engines
 * with objects, such as scoped Bindings, in hosting applications.  Each scope is a set
 * of named attributes whose values can be set and retrieved using the
 * <code>ScriptContext</code> methods. ScriptContexts also expose Readers and Writers
 * that can be used by the ScriptEngines for input and output.
 *
 * @author Mike Grogan
 * @since 1.6
 */
public interface ScriptContext {

五、参考

Java 8 的 Nashorn 脚本引擎教程
参考URL:
Java新特性之Nashorn的实例详解
参考URL:
Java 8 Nashorn 教程
参考URL:
[推荐]Nashorn——在JDK 8中融合Java与JavaScript之力
参考URL:
官方参考URL: https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/ java – 每个线程应该使用单独的ScriptEngine和CompiledScript实例吗?
参考URL: http://www.voidcn.com/article/p-czfxznxr-bsx.html