Java 脚本化编程指南
Java 脚本化编程指南
Java脚本化API为谁准备?
脚本语言的一些有用的特性是:
- 方便:大多数脚本语言都是动态类型的。您通常可以创建新的变量,而不声明变量类型,并且您可以重用变量来存储不同类型的对象。此外,脚本语言往往会自动执行许多类型的转换,例如, 必要时 将数字10转换为“10”。 
- 开发快速原型:您可以避免编辑编译运行周期,只使用“编辑运行”! 
 应用扩展/定制:你可以“具体化”的部分应用程序,例如一些配置脚本,业务逻辑/规则和财务应用中的数学表达式 。
- 为应用添加命令行模式,用于调试、运行时配置/部署时间。现在大多数应用程序都有一个基于Web的GUI配置工具。但是系统管理员/部署人员常常喜欢命令行工具。一个“标准”的脚本语言可以用来实现这个目的,而不是发明特设的脚本语言。 - Java 脚本 API 是一种独立于框架的脚本语言,使用来自于Java代码的脚本引擎 。通过java脚本API,可以使用Java语言编写定制/可扩展的应用程序并将自定义脚本语言选择留给最终用户 。Java 应用程序开发者不需要在开发过程中选择扩展语言。如果你使用JSR-223 API来编写应用,那么你的用户可以使用任何JSR-223兼容的脚本语言。 
脚本包
Java 脚本功能是在javax.script包中。这是一个比较小的,简单的API。脚本的出发点是 ScriptEngineManager类。一个ScriptEngineManager对象可以通过jar文件的服务发现机制发现脚本引擎。它也可以实例化脚本引擎来解释使用特定的脚本语言编写的脚本。使用脚本编程接口的最简单的方法如下:
创建一个ScriptEngineManager对象 
从ScriptEngineManager获取 ScriptEngine对象 
使用ScriptEngine的eval方法执行脚本
现在,是时候看一些样本代码了。了解一些JavaScript有助于阅读这些例子,但不是强制的。
实例
Hello,World
从ScriptEngineManager实例中,我们通过 getEngineByName 方法得到一个JavaScript引擎实例。通过脚本引擎的eval方法来执行给定的JavaScript代码。为简便起见,本例以及随后的例子中,我们不对异常进行处理。javax.script API有检查和运行时异常,你必须妥善处理异常。
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class Case1 {
    public static void main(String[] args) throws ScriptException {
        //获取脚本引擎管理器
        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        //根据引擎名获取JavaScript引擎
        ScriptEngine engine = scriptEngineManager.getEngineByName("JavaScript");
        //执行JavaScript方法
        engine.eval("print('hello world')");
    }
}
执行一个脚本文件
在这个例子中,我们调用eval方法来接收java.io.Reader作为输入源。读入的脚本被执行。这种方式能够成文件执行脚本,用相关的输入流对象读取URL和资源
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.nio.CharBuffer;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class ExecuteOneJsFile {
    public static void main(String[] args) throws FileNotFoundException, ScriptException {
        // 引擎manager
        ScriptEngineManager factory = new ScriptEngineManager();
        // 创建一个 JavaScript engine
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        // 下面是打印文件的内容读东西
        String jsText = null;
        CharBuffer cbuf = null;
        String jsFilePath = "d:\\test.js";
        File file = new File(jsFilePath);
        FileReader fReader = null;
        try {
            fReader = new FileReader(file);
//          fReader.mark((int)file.length());不支持重置文件到开始
            cbuf = CharBuffer.allocate((int) file.length());
            fReader.read(cbuf);
            jsText = new String(cbuf.array());
            System.out.println(jsText);
            // 执行D盘下面的test.js文件
//          fReader.reset();//重置fRead的指针为文件的开始部分,这里需要配合mark函数的使用(不支持)
            engine.eval( new FileReader(file));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
程序运行结果
脚本变量
当你的java应用程序嵌入脚本引擎和脚本,你可能希望将您的应用程序对象为全局变量暴露于脚本中。这个例子演示了如何将您的应用程序对象作为全局变量暴露于脚本中。我们在应用程序中创建一个 java.io.File对象作为全局变量,名称是file。该脚本可以访问变量,例如,它可以调用它的公共方法。注意访问java对象、领域和方法的语法依赖于脚本语言。JavaScript支持最“自然”的类似java的语法。
package com.neil.java;
import java.io.File;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class VariantWithJavaxScript {
    public static void main(String[] args) throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        //创建一个Java的对象
        File file = new File("readme.txt");
        //将file放入JavaScript中
        //key4file是key,可以使用该key来访问该变量
        engine.put("key4file", file);
        //执行使用java的api来调用file的getAbsolutePath方法
        System.out.println(file.getAbsolutePath());
        //在js语句中调用file的getAbsolutePath方法;
        engine.eval("print(key4file.getAbsolutePath())");
    }
}
程序运行结果
调用脚本函数和方法
有些时候,你可能需要多次调用一个特定脚本函数,例如你的应用程序菜单功能可能由脚本来实现。在菜单中的操作事件处理程序中,可能需要调用一个特定的脚本函数。下面的示例演示在Java代码调用一个特定的脚本。
package com.neil.java;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class FunctionWithJavaxScript {
    public static void main(String[] args) throws ScriptException, NoSuchMethodException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        // 编写一个JavaScript函数
        String script = "function hello(name) { print('Hello, ' + name); }";
        // 执行上面的语句,就会在JavaScript运行环境上创建一个hello(name)的函数
        engine.eval(script);
        // javax.script.Invocable is an optional interface.
        // Check whether your script engine implements or not!
        // Note that the JavaScript engine implements Invocable interface.
        //判断JavaScript引擎是不是可调用的接口,来执行hello函数
        Invocable inv = (Invocable) engine;
        // 调用全局函数hello,并为其传入一个参数""
        inv.invokeFunction("hello", "neil,牛中超");
    }
}
程序运行结果
如果你的脚本语言是基于对象(如JavaScript)或面向对象的,你可以在脚本对象上调用脚本方法。
package com.neil.java;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class FunctionOfObjectWithJavaxScript {
    public static void main(String[] args) throws ScriptException, NoSuchMethodException {
        // 获取manager和engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        /*
         * 创建一个js语句 js的语句首先创建一个object对象,该对象的成员hello指向的是一个名为hello的函数
         * hello的函数是print
         */
        String js = "var object = new Object(); object.hello = function(name) {print( name );}";
        // 使用eval指向上面的js语句,将object刷到JavaScript运行环境上
        engine.eval(js);
        // 判断当前的engine是否可调用
        Invocable invocable = (Invocable) engine;
        // 在Java上创建一个Object对象,来获取object
        // get方法和上面的put方法很类似,用于java和javascript进行对象的互相访问
        Object obj = engine.get("object");
        //调用object的hello方法
        invocable.invokeMethod(obj, "hello", "Neil,牛中超");
    }
}
程序运行结果
通过脚本实现Java接口
有些时候通过脚本函数或者方法可以很方便的实现java接口,而不是在Java中调用。同时,通过接口我们可以避免在很多地方使用javax.script API接口。我们可以得到一个接口实现者对象并将其传递给不同的Java api。下面的例子演示了通过脚本实现java.lang.Runnable接口。
package com.neil.java;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class CallJavaInterface {
    public static void main(String[] args) throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        //在js里面创建一个run方法,作为下面的Java中的Runnable对象的run方法
        String js = "function run(){print('hello, Neil,牛中超')}";
        //执行该js,将run方法刷到JavaScript运行环境中
        engine.eval(js);
        //获取JavaScript引擎的可调用接口
        Invocable invocable = (Invocable) engine;
        //从engine根据传入的类类型(本例中传入的是Runnable类型)来获取一个Runnable接口
        //且javascript的invocable.getInterface方法会自动从js引擎中
        //根据相同的函数名来实例化Runnable接口的方法
        //也就是,getInterface会自动把上面创建的run方法用来实例化runnable的run方法
        Runnable runnable = invocable.getInterface(Runnable.class);
        //在java中创建一个线程,并启动
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
程序运行结果
如果你的脚本语言是基于对象或者面向对象的,可以通过脚本对象的脚本方法来实现Java接口。这避免了不得不调用脚本全局函数的接口方法。脚本对象可以存储接口实现状态。
package com.neil.java;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class CallJavaInterfaceWithJsObjMethod {
    public static void main(String[] args) throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        //创建一个对象,并给该对象实现一个run方法,run方法不能带参数
        String js = "var yourName = new Object(); yourName.run = function(){ print('Hello, Neil,牛中超')}";
        //执行上面的js语句,将yourName刷到JavaScript运行环境上
        engine.eval(js);
        //获取engine的可调用接口
        Invocable invocable = (Invocable) engine;
        //获取JavaScript运行环境上的yourName对象
        Object object = engine.get("yourName");
        //用object中的方法来实现Runnable需要实现的的同名方法(也就是run方法)
        Runnable runnable = invocable.getInterface(object,Runnable.class);
        //创建线程,并启动
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
程序运行结果
脚本的多作用域
在script variables例子中,我们看到怎样将应用对象暴露为脚本的全局变量。它有可能暴露为多个全局的作用域 。 单作用域是javax.script.Bindings的实例中. 这个借口派生至java.util.Map<String, Object>。 scope键值对的集合,其中键为非空、非空字符串。 多scopes是 javax.script.ScriptContext接口支持的。支持一个或多个脚本上下文与相关的域绑定。默认情况下, 每一个脚本引擎都有一个默认的脚本上下文。 默认的脚本上下文有至少一个域叫 ENGINE_SCOPE。不同域的脚本上下文支持可以通过getscopes方法获取。
package com.neil.java;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
public class MultiScopes {
    public static void main(String[] args) throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");
        //向JavaScript引擎默认的上下文范围中写入变量
        engine.put("name", "Neil");
        engine.eval("print(name)");
        //创建一个新的上下文
        ScriptContext context = new SimpleScriptContext();
        //获取一个用来给新创建的上下文制定的ENGINE_SCOPE
        Bindings engineScope = context.getBindings(ScriptContext.ENGINE_SCOPE);
        //在新的上下文的ENGINE_SCOPE中设置一个name变量
        engineScope.put("name", "牛中超");
        //从新的上下文中获取name
        engine.eval("print(name)",context);
    }
}
程序运行结果
JavaScript 脚本引擎
Sun的JDK 6中包含了一个基于Mozilla Rhino JavaScript脚本引擎。 这个引擎是基于版本为1.6R2的Mozilla Rhino。多数Rhino实现都被包含在内。少部分组件由于大小和安全原因被排除了:
- JavaScript转字节码编译 (也称- 优化器)。此功能依赖一个类生成库。 去掉本功能意味着:- JavaScript是解释执行,且不影响脚本执行,因为优化器是透明的。
- Rhino的- JavaAdapter也被去掉了。- JavaAdapter是一个- JavaScript可扩展- Java类和- JavaScript可实现- Java接口功能。此功能也是需要类生成库的。我们把- Rhino的- JavaAdapter替换
 为- Sun实现的- JavaAdapter。在- Sun的实现中,仅仅实现了- JavaScript对象可实现- Java单接口功能。例如,下面的代码会正确执行。- var v = new java.lang.Runnable() {
 run: function() { print('hello'); }
 }
 v.run();- 在大多数情况下, - JavaAdapter是采用匿名类语法来实现单接口。 使用- JavaAdapter来扩展- Java类或实现多接口并不常见。
- E4X (ECMAScript for XML - ECMA Standard 357)被去掉了. 使用- XML JavaScript代码会产生一个语法错误. 请注意,- E4X支持- ECMAScript标准是可选的-省略E4X的实现是被支持也是兼容- ECMAScript。
- Rhino的命令行工具- (Rhino shell, debugger 等)没有被包含在内。但你可以用使用- jrunscript来代替。
JavaScript与Java的通信
在大多数情况下,访问Java类、对象和方法很简单。从JavaScript中访问属性和方法与同Java中一样。这里,我们突出JavaScript Java访问的重要方面。更多的细节请阅读。下面是一些JavaScript访问Java的代码片段。本节需要一些JavaScript知识。如果你打算使用JSR-223中非JavaScript脚本语言,那么本节可以跳过。
引入Java 包, 类
内置的函数importPackage和importClass可以用于引入Java 包和类。 
全局变量Packages也可以用于访问Java包。例如: Packages.java.util.Vector, Packages.javax.swing.JFrame. 请注意: java是 Packages.java的快捷引用。还有一些等价的快捷引用前缀 :javax, org, edu, com, net, 所以几乎所有的JDK 平台下的类都可以不使用Packages 前缀而访问到
package com.neil.java;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class ImportPackage {
    public static void main(String[] args) throws FileNotFoundException, ScriptException {
        String filePath = "d:\\import.js";
        File file = new File(filePath);
        FileReader fileReader = new FileReader(file);
        if(fileReader!=null)
        {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("JavaScript");
            //执行
            engine.eval(fileReader);
        }
    }
}
import.js的文件内容如下
// Import Java packages and classes
// like import package.*; in Java
Packages.java.awt;
// like import java.awt.Frame in Java
Packages.java.awt.Frame;
// Create Java Objects by "new ClassName"
var frame = new java.awt.Frame("hello");
// Call Java public methods from script
frame.setVisible(true);
// Access "JavaBean" properties like "fields"
print(frame.title)程序运行结果 
需要注意的是上面创建的一个JFrame没有添加关闭事件,需要在任务管理器中关闭掉其所以来的 java线程。
请注意,java.lang不是默认引入的 (与Java不同),因为会与JavaScript's内置的 Object, Boolean, Math 等冲突。
importPackage 和importClass函数”污染” 了JavaScript中的全局变量。为了避免这种情况,你可以使用JavaImporter。
// create JavaImporter with specific packages and classes to import
var SwingGui = new JavaImporter(javax.swing,
                            javax.swing.event,
                            javax.swing.border,
                            java.awt.event);
with (SwingGui) {
    // within this 'with' statement, we can access Swing and AWT
    // classes by unqualified (simple) names.
    var mybutton = new JButton("test");
    var myframe = new JFrame("test");
}创建和使用Java的数组
在   JavaScript中,创建一个对象时与Java中一样,而创建Java数组时需要显式的使用Java反射。但一旦创建好后,访问其中的元素或获取大小就和Java中一样。 另外,也可以使用脚本数组用在Java方法中期望的Java数组(因为可以自动转换)。所以在大多数情况下我们不需要显式地创建Java数组。
// create Java String array of 5 elements
var a = java.lang.reflect.Array.newInstance(java.lang.String, 5);
// Accessing elements and length access is by usual Java syntax
a[0] = "scripting is great!";
print(a.length);实现Java 接口
在JavaScript中,可以使用Java匿名类语法形式实现Java中接口:
var r  = new java.lang.Runnable() {
    run: function() {
        print("running...\n");
    }
};
// "r" can be passed to Java methods that expect java.lang.Runnable
var th = new java.lang.Thread(r);
th.start();当接口中只有一个需要实现的方法时,你可以自己传入脚本的函数(因为可以自动转换)。
function func() {
     print("I am func!");
}
// pass script function for java.lang.Runnable argument
var th = new java.lang.Thread(func);
th.start();重载
Java方法是使用参数类型重载的。在Java中,重载发生在编译阶段 (执行javac)。当脚本中调用Java方法时,脚本的翻译器或编译器需要选择适当的方法。对于JavaScript引擎,您不需要做任何特别的——正确的Java方法重载变体是根据参数类型选择的。 但有时,您可能希望(或有)显式地选择一个特定的过载变体。
var out = java.lang.System.out;
// select a particular println function
out["println(java.lang.Object)"]("hello");更多JavaScript的Java方法重载细节阅读
自定义脚本引擎
我们不会覆盖的JSR-223兼容脚本引擎实现细节. 至少, 您需要实现javax.script.ScriptEngine和javax.script.ScriptEngineFactory接口。 抽象类javax.script.AbstractScriptEngine 提供了一些ScriptEngine接口中定义的方法。
在开始实现JSR-223引擎之前,您可能需要下载http://scripting.dev.java.net工程。这个工程维护了一些流行的开源脚本语言的 JSR-223实现。
引用 
1. JSR-223 Scripting for the Java Platform 
2. JavaScript Developer Connection 
3. Java Method Overloading and LiveConnect 3 
4. Rhino:JavaScript for Java 
5. Scripting Java (from JavaScript) 
6. scripting.dev.java.net project
Java 脚本化编程指南的更多相关文章
- Rhino 是一个完全使用Java语言编写的开源JavaScript实现。Rhino通常用于在Java程序中,为最终用户提供脚本化能力。它被作为J2SE 6上的默认Java脚本化引擎。
		https://developer.mozilla.org/zh-CN/docs/Mozilla/Projects/Rhino 
- 【Todo】【读书笔记】Java多线程编程指南-设计模式篇
		下了这本书<Java多线程编程指南-设计模式篇>, 还有另一本<JAVA多线程设计模式>,据说内容有重复,结合着看. 
- Java并发编程-并发工具包(java.util.concurrent)使用指南(全)
		1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包.这个包包含有一系列能够让 Ja ... 
- JavaScript权威指南--脚本化CSS
		知识要点 客户端javascript程序员对CSS感兴趣的是因为样式可以通过脚本编程.脚本化css启用了一系列有趣的视觉效果.例如:可以创建动画让文档从右侧“滑入”.创造这些效果的javascript ... 
- JavaScript权威指南--脚本化文档
		知识要点 脚本化web页面内容是javascript的核心目标. 第13章和14章解释了每一个web浏览器窗口.标签也和框架由一个window对象所示.每个window对象有一个document对象, ... 
- 权威指南之脚本化jquery
		jqury函数 jquery()($())有4种不同的调用方式 第一种是最常用的调用方式是传递css选择器(字符串)给$()方法.当通过这种方式调用时,$()方法会返回当前文档中匹配该选择器的元素集. ... 
- Java 并发工具包 java.util.concurrent 用户指南
		1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包.这个包包含有一系列能够让 Ja ... 
- KVC/KVO原理详解及编程指南
		一.简介 1.KVC简介 2.KVO简介 二.KVC相关技术 1.Key和Key Path 2.点语法和KVC 3.一对多关系(To-Many)中的集合访问器方法 4.键值验证(Key-Value V ... 
- 物联网操作系统HelloX应用编程指南
		HelloX操作系统应用编程指南 HelloX应用开发概述 可以通过三种方式,在HelloX操作系统基础上开发应用: 1. 以内部命令方式实现应用,直接编译链接到HelloX的内核she ... 
随机推荐
- 填坑实录   Android Studio 利用 ADB WIFI 插件实现真机无线调试
			总是用模拟器,小破本的渣内存无法承受,同时模拟器的版本大多停在4.4,无法体现Android 5.0.6.0 的版本特性,因此决定利用 Android Studio 的插件实现真机无线调试. 步骤如下 ... 
- 什么是测试开发工程师-google的解释
			什么是测试开发工程师-google的解释 “ 软件测试开发工程师[SET or Software Engineer in Test],和软件开发工程师一样是开发工程师,主要负责软件的可测试性.他们参与 ... 
- 老李分享:大数据框架Hadoop和Spark的异同
			poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821478,咨询电话010-845052 ... 
- 翻译一篇文章:It's Difficult to Grow a Test Developer(成为测试开发工程师的艰辛)
			翻译一篇文章:It's Difficult to Grow a Test Developer(成为测试开发工程师的艰辛) 以下文章是送给来poptest学习测试开发工程师的学员们,很多人想测试工程 ... 
- apache的配置参数
			#ErrorDocument 500 "The server made a boo boo."#ErrorDocument 404 /missing.html 1.Document ... 
- Java中log4j的使用
			前言 距离上一篇文章又过去好长时间了,这段时间一直忙于工作,已经从net彻底转向Java了.工作也慢慢的步入正轨了,自己独自完成了一个小项目,不过工作中遇到了一些问题,还是得到了同学和同事的帮助.本来 ... 
- Java 中的 String 类常用方法
			字符串广泛应用在Java编程中,在Java中字符串属于对象,String 类提供了许多用来处理字符串的方法,例如,获取字符串长度.对字符串进行截取.将字符串转换为大写或小写.字符串分割等. Strin ... 
- 这个demo是为解决IQKeyboardManager和Masonry同时使用时,导航栏上移和make.right失效的问题
			原文链接在我的个人博客主页 (一).引言: 在 IQKeyboardManager 和 Masonry 同时使用时,导航栏上移和make.right失效等问题多多. 其实我们完美的效果应该是这样的:* ... 
- Android批量验证渠道、版本号
			功能:可校验单个或目录下所有apk文件的渠道号.版本号使用说明:1.copy需要校验的apk文件到VerifyChannelVersion目录下2.双击运行VerifyChannelVersion.b ... 
- 基于bootstrap的bootstrap-editable插件实现即时编辑功能
			1.引用基本css和js: <link href="bootstrap3/css/bootstrap.min.css" rel="stylesheet" ... 
