记录生活中的点点滴滴

0%

Java脚本化编程

脚本(Script),是使用一种特定的描述性语言,依据一定的格式编写的可执行文件。

脚本包

Java 脚本功能是在javax.script包中。这是一个比较小的,简单的API。脚本的出发点是 ScriptEngineManager类。一个ScriptEngineManager对象可以通过jar文件的服务发现机制发现脚本引擎。它也可以实例化脚本引擎来解释使用特定的脚本语言编写的脚本。使用脚本编程接口的最简单的方法如下:

  1. 创建一个ScriptEngineManager对象
  2. ScriptEngineManager获取 ScriptEngine对象
  3. 使用ScriptEngineeval方法执行脚本

下面给出一些例子:

举些栗子

hello world

啥也不说了,直接给俺 hello world 起来,我们用 JavaScript 来输出 hello world 吧。

1
2
3
4
5
6
7
8
public static void main(String[] args) throws ScriptException {
//获取脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
//根据引擎名获取JavaScript引擎
ScriptEngine engine = manager.getEngineByName("JavaScript");
//执行JavaScript方法
engine.eval("print('hello world!')");
}

执行如图所示:

还是可以滴!虽然它提示好像要被移除什么的,甭管这些。

执行脚本文件

执行一行代码权当我们玩玩,下面我们执行一个 js 文件看看。

怎么执行?我们调用eval方法来接收java.io.Reader作为输入源即可。

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws FileNotFoundException, ScriptException {
//获取JavaScript引擎
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

//对应文件及字符输入流
File file = new File("test.js");
FileReader fr = new FileReader(file);

//执行脚本文件
engine.eval(fr);
}

test.js 文件中可以随便写一些 JavaScript 代码:

1
2
3
4
5
print("this is a JavaScript sccipt...")
function f(a,b) {
return a+b
}
print(f(100,200))

我们执行main方法,结果如下:

脚本变量

我们在我们的Java程序中嵌入脚本,但有的时候希望脚本能够访问我们Java程序的一些对象,下面就给出了将对象作为全局变量暴露在脚本中。

我们先确保我们有一个 Person 类,它有姓名和年龄两个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//将对象作为全局变量暴露在脚本中
public class ScriptVar {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

Person person = new Person("张三", 20);
//把person放入脚本中作为全局变量
engine.put("person",person);

//执行js代码
engine.eval("print(typeof(person))");
engine.eval("print(person)");
}
}

上述代码就把我们创建的一个 Person 对象放入到脚本中,当然在脚本中我们也能获取它。执行结果如下:

调用脚本函数和方法

下面演示一下在Java代码调用一个特定的脚本函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

//编写js函数,执行脚本
String script = "function hello(name){ print('Hello, '+name+'!') }";
engine.eval(script);

//判断JavaScript引擎是不是可调用的接口,来执行hello函数
Invocable inv = (Invocable) engine;

//调用全局函数hello,并为其传入一个参数""
inv.invokeFunction("hello", "张三");
}

执行结果:

脚本语言是基于对象(如JavaScript)或面向对象的,你可以在脚本对象上调用脚本方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void Test1() throws ScriptException, NoSuchMethodException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

//编写js函数,为o对象增加一个hello方法,执行脚本
String script = "var o = new Object();o.hello = function(name){ print('Hello, '+name+'!') }";
engine.eval(script);

//get和put方法很像,用于java和javascript进行对象的互相访问
Object o = engine.get("o");

//判断JavaScript引擎是不是可调用的接口,来执行hello函数
Invocable inv = (Invocable) engine;

//调用o的hello方法,传入的参数是李四
inv.invokeMethod(o,"hello","李四");
}

执行结果:

通过脚本实现Java接口

有些时候通过脚本函数或者方法可以很方便的实现java接口,而不是在Java中调用。同时,通过接口我们可以避免在很多地方使用javax.script API接口。我们可以得到一个接口实现者对象并将其传递给不同的Java api。下面的例子演示了通过脚本实现java.lang.Runnable接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

//在js里面创建一个run方法,作为下面的Java中的Runnable对象的run方法
String script = "function run(){ print('执行了run方法...') }";
engine.eval(script);

//获取JavaScript引擎的可调用接口
Invocable inv = (Invocable) engine;

//从engine根据传入的类类型(本例中传入的是Runnable类型)来获取一个Runnable接口
//且javascript的invocable.getInterface方法会自动从js引擎中
//根据相同的函数名来实例化Runnable接口的方法
//也就是,getInterface会自动把上面创建的run方法用来实例化runnable的run方法
Runnable runnable = inv.getInterface(Runnable.class);

//创建一个线程并启动
Thread thread = new Thread(runnable);
thread.start();
}

输出结果:

1
2
Warning: Nashorn engine is planned to be removed from a future JDK release
执行了run方法...

脚本的多作用域

script variables例子中,我们看到怎样将应用对象暴露为脚本的全局变量。它有可能暴露为多个全局的作用域 。 单作用域是javax.script.Bindings的实例中. 这个借口派生至java.util.Mapscope键值对的集合,其中键为非空、非空字符串。 多scopesjavax.script.ScriptContext接口支持的。支持一个或多个脚本上下文与相关的域绑定。默认情况下, 每一个脚本引擎都有一个默认的脚本上下文。 默认的脚本上下文有至少一个域叫 ENGINE_SCOPE。不同域的脚本上下文支持可以通过getscopes方法获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

engine.put("name","张三");
engine.eval("print(name)");

//创建一个新的上下文
SimpleScriptContext context = new SimpleScriptContext();
//获取一个用来给新创建的上下文制定的ENGINE_SCOPE
Bindings engine_scope = context.getBindings(ScriptContext.ENGINE_SCOPE);
//在作用域中设置一个name变量
engine_scope.put("name","李四");

//从新的上下文中获取name
engine.eval("print(name)",context);
}

输出结果:

1
2
3
Warning: Nashorn engine is planned to be removed from a future JDK release
张三
李四

JavaScript与Java的通信

在大多数情况下,访问Java类对象方法很简单。从JavaScript中访问属性和方法与同Java中一样。这里,我们突出JavaScript Java访问的重要方面。下面是一些JavaScript访问Java的代码片段。

引入Java 包, 类

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws FileNotFoundException, ScriptException {
File file = new File("packge.js");
FileReader fr = new FileReader(file);

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");

engine.eval(fr);

}

关键在于 packge.js 这个文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
// 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)

执行结果:

请注意,java.lang不是默认引入的 (与Java不同),因为会与JavaScript's内置的 Object, Boolean, Math 等冲突。

importPackageimportClass函数”污染” 了JavaScript中的全局变量。为了避免这种情况,可以使用JavaImporter

实现Java 接口

JavaScript中,可以使用Java匿名类语法形式实现Java中接口:

1
2
3
4
5
6
7
8
9
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();

当接口中只有一个需要实现的方法时,你可以自己传入脚本的函数(因为可以自动转换)。

1
2
3
4
5
6
7
function func() {
print("I am func!");
}

// pass script function for java.lang.Runnable argument
var th = new java.lang.Thread(func);
th.start();