Сегодня многие Java-разработчики заинтересованы в использовании языков сценариев на платформе Java, но динамический язык не всегда можно скомпилировать в байт-код Java. В некоторых случаях быстрее и эффективнее просто написать сценарии отдельных частей Java-приложения или вызывать нужные объекты Java внутри сценария.
Вот здесь-то и приходит на помощь javax.script. API Java Scripting, введенный в Java 6, ликвидирует разрыв между удобными компактными языками сценариев и мощной экосистемой Java. Он позволяет быстро интегрировать с кодом Java практически любой язык сценариев, что открывает широкие возможности при решении мелких задач за чужой счет.
1. Выполнение JavaScript с помощью jrunscript
Каждый новый выпуск платформы Java приносит с собой новый набор инструментов командной строки, скрытых в дебрях каталога JDK bin. Java 6 не стал исключением, и jrunscript оказался немалым дополнением к утилитам платформы Java.
Рассмотрим простую задачу написания сценария командной строки для контроля производительности. Инструмент должен каждые 5 секунд выполнять код jmap (позаимоствованный из предыдущей статьи этого цикла) над процессом Java, чтобы определить, как быстро работает этот процесс. Обычно такие задачи решает оболочка командной строки, но в данном случае серверное приложение развернуто на нескольких различных платформах, включая Windows® и Linux®. Системные администраторы понимают, что написать сценарий, который работает на обеих платформах, трудно. Стандартное решение заключается в том, чтобы написать пакетный файл для Windows и сценарий для оболочки UNIX®, а затем просто синхронизировать их.
Но, как известно любому читателю The Pragmatic Programmer, это серьезное нарушение принципа DRY (don't repeat yourself ― не повторяйся) и чревато ошибками и дефектами. Лучше всего написать какой-нибудь независимый от ОС сценарий, работающий на всех платформах.
Конечно, язык Java не зависит от платформы, но на самом деле это не вариант «системного» языка. Здесь нужен язык сценариев — такой как JavaScript.
Листинг 1 создает базовую оболочку для того, что нам нужно.
Листинг 1. periodic.js
while (true)
{
echo("Hello, world!");
} |
Большинству Java-разработчиков уже знаком JavaScript (или ECMAScript; JavaScript ― это диалект ECMAScript, права на который принадлежат компании Netscape) благодаря вынужденному взаимодействию с Web-браузерами. Вопрос в том, как системному администратору запускать этот сценарий?
Решением, конечно же, служит утилита jrunscript, которая входит в состав JDK, как показано в листинге 2.
Листинг 2.jrunscript
C:\developerWorks\5things-scripting\code\jssrc>jrunscript periodic.js Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! ... |
Заметим, что можно также использовать цикл for для выполнения сценария заданное количество раз перед выходом. jrunscript позволяет делать практически все, что обычно делают с помощью JavaScript. Единственное исключение: это делается не в браузере, и, следовательно, нет никакой DOM. Поэтому доступные функции и объекты верхнего уровня несколько отличаются.
Поскольку в состав JDK Java 6 входит механизм Rhino ECMAScript, jrunscript может выполнить любой код ECMAScript из файла (как показано здесь) или в более интерактивной среде оболочки REPL ("Read-Evaluate-Print-Loop"). Чтобы получить доступ к оболочке REPL, достаточно запустить jrunscript без параметров.
2. Доступ к Java-объектам из сценария
Возможность написать код JavaScript/ECMAScript ― это, конечно, хорошо, но мы не хотим повторять с нуля все, что мы используем на языке Java — это будет противоречить нашей цели. К счастью, все сделанное с использованием API Java Scripting имеет полный доступ ко всей экосистеме Java, потому что в основе своей это все тот же байт-код Java. Таким образом, возвращаясь к исходной задаче, можно запускать процессы из платформы Java с помощью традиционного вызова Runtime.exec(), как показано в листинге 3.
Листинг 3. Runtime.Exec() запускает jmap
var p = java.lang.Runtime.getRuntime().exec("jmap", [ "-histo", arguments[0] ])
p.waitFor()
|
Массив arguments представляет собой стандартную для ECMAScript встроенную ссылку на аргументы, передаваемые этой функции. В случае среды сценариев верхнего уровня это массив аргументов, передаваемый в сам сценарий (параметры командной строки). Так что представленный в листинге 3 сценарий ждет аргумента, содержащего VMID процесса Java.
Иначе, мы могли бы воспользоваться тем, что jmap ― это Java-класс, и просто вызвать его метод main(), как показано в листинге 4. Этот подход устраняет необходимость в передаче потоков in/out/err объекта Process.
Листинг 4. JMap.main()
var args = [ "-histo", arguments[0] ] Packages.sun.tools.jmap.JMap.main(args) |
Синтаксис Packages представляет собой нотацию Rhino ECMAScript, которая используется для обозначения пакета Java вне основных пакетов java.*, которые уже настроены на Rhino.
3. Вызов сценариев из кода Java
Вызов Java-объектов из сценария ― лишь половина дела. Среда сценариев Java позволяет также вызывать сценарии из кода Java. Для этого достаточно создать экземпляр ScriptEngine, а затем загрузить сценарий и оценить его, как показано в листинге 5.
Листинг 5. Вызов сценариев на платформе Java
import java.io.*;
import javax.script.*;
public class App
{
public static void main(String[] args)
{
try
{
ScriptEngine engine =
new ScriptEngineManager().getEngineByName("javascript");
for (String arg : args)
{
FileReader fr = new FileReader(arg);
engine.eval(fr);
}
}
catch(IOException ioEx)
{
ioEx.printStackTrace();
}
catch(ScriptException scrEx)
{
scrEx.printStackTrace();
}
}
} |
Метод eval() может работать и непосредственно со строкой, так что сценарий не обязательно извлекать из файла в файловой системе — он может поступать из базы данных, от пользователя или даже создаваться внутри приложения в зависимости от обстоятельств и действий пользователя.
4. Привязка объектов Java к пространству сценария
Простого вызова сценария недостаточно: сценарии часто должны взаимодействовать с объектами, созданными в среде Java. В таких случаях хозяйская среда Java должна создавать объекты и привязывать их таким образом, чтобы сценарий мог легко находить и использовать их. Эта задача для объекта ScriptContext показана в листинге 6.
Листинг 6. Связывание объектов для сценариев
import java.io.*;
import javax.script.*;
public class App
{
public static void main(String[] args)
{
try
{
ScriptEngine engine =
new ScriptEngineManager().getEngineByName("javascript");
for (String arg : args)
{
Bindings bindings = new SimpleBindings();
bindings.put("author", new Person("Ted", "Neward", 39));
bindings.put("title", "5 Things You Didn't Know");
FileReader fr = new FileReader(arg);
engine.eval(fr, bindings);
}
}
catch(IOException ioEx)
{
ioEx.printStackTrace();
}
catch(ScriptException scrEx)
{
scrEx.printStackTrace();
}
}
} |
Обратиться к связанному объекту легко — имя связанного объекта вводится на уровне сценария в качестве члена глобального пространства имен, поэтому использовать объект Person из Rhino так же просто, как это делается в листинге 7.
Листинг 7. Кто написал эту статью?
println("Hello from inside scripting!")
println("author.firstName = " + author.firstName)
|
Как видите, свойства в стиле JavaBeans сводятся к прямому доступу к имени, как если бы это были поля.
5. Компиляция часто используемых сценариев
Слабым местом языков сценариев всегда было быстродействие. Причина в том, что в большинстве случаев язык сценариев интерпретируется «на лету», и время и циклы процессора тратятся на то, чтобы проанализировать и проверить текст, когда он выполняется. Многие языки сценариев, работающие на JVM, в конечном итоге преобразуют входной код в байт-код Java, по крайней мере, при первом анализе и проверке; при завершении работы Java-программы результат такой динамической компиляции пропадает. Сохранение часто используемых сценариев в форме байт-кода дает ощутимые преимущества в производительности.
Это можно сделать естественным и рациональным способом с помощью API Java Scripting. Если возвращаемый ScriptEngine реализует интерфейс Compilable, то метод compile этого интерфейса можно использовать для компиляции сценария в экземпляр CompiledScript (передается как String или Reader), который затем будет вновь и вовь применяться для оценки (eval()) скомпилированного кода с различными привязками. Это показано в листинге 8.
Листинг 8. Компиляция интерпретируемого кода
import java.io.*;
import javax.script.*;
public class App
{
public static void main(String[] args)
{
try
{
ScriptEngine engine =
new ScriptEngineManager().getEngineByName("javascript");
for (String arg : args)
{
Bindings bindings = new SimpleBindings();
bindings.put("author", new Person("Ted", "Neward", 39));
bindings.put("title", "5 Things You Didn't Know");
FileReader fr = new FileReader(arg);
if (engine instanceof Compilable)
{
System.out.println("Compiling....");
Compilable compEngine = (Compilable)engine;
CompiledScript cs = compEngine.compile(fr);
cs.eval(bindings);
}
else
engine.eval(fr, bindings);
}
}
catch(IOException ioEx)
{
ioEx.printStackTrace();
}
catch(ScriptException scrEx)
{
scrEx.printStackTrace();
}
}
} |
В большинстве случаев во избежание многократной перекомпиляции одного и того же сценария экземпляр CompiledScript должен храниться где-то в долговременной памяти (например,в servlet-context). Однако при изменении содержимого сценария необходимо создать новый CompiledScript, отражающий эти изменения; после компиляции CompiledScript больше не выполняет содержимое первоначального файла сценария.
API Java Scripting ― огромный шаг вперед в направлении расширения охвата и функциональности Java-программ, который приносит в среду Java выгоды, связанные с языками сценариев. В сочетании с jrunscript — использовать который, конечно, гораздо легче, чем писать программы — javax.script несет разработчикам Java преимущества языков сценариев, таких как Ruby (JRuby) и ECMAScript (Rhino), без необходимости отказываться от экосистемы и масштабируемости среды Java.
Далее в цикле 5 секретов…: JDBC.
- Оригинал статьи: 5 things you didn't know about ... the Java Scripting API.
- Пять секретов...: цикл статей с полезными советами по Java-программированию.
- Invoke dynamic languages dynamically, Part 1: Introducing the Java scripting API (Tom McQueeney, developerWorks, сентябрь 2007 г.): первая часть этого цикла из двух статей знакомит читателя с особенностями API Java scripting; вторая описывает многие мощные приложения.
- JavaScript EE, Part 3: Use Java scripting API with JSP (Andrei Cioroianu, developerWorks, июнь 2009 г.): подробнее об объединении JavaScript с платформой Java и о том, как создавать интерфейсы пользователя Ajax, которые продолжают работать, при отключенной поддержке JavaScript в Web-браузере.
- Инструменты и утилиты JDK: об экспериментальных инструментах контроля и отладки, обсуждавшихся в Пяти секретах, с упором на контроль производительности, включая
jmap.