Пять секретов… API Java Scripting

Простой способ создания сценариев на платформе Java

Для некоторых проектов языка Java™ более чем достаточно, языки же сценариев, как известно, не блещут производительностью. API Java Scripting (javax.script), которому посвящена эта статья, соединяет преимущества тех и других, позволяя вызывать сценарии из Java-программ, и наоборот.

Тед Ньюворд, директор, Neward & Associates

Тед Ньюворд (Ted Neward) является директором компании “Neward & Associates”. Он занимается консультированием, преподаванием и презентациями продуктов на основе Java, .NET, XML-сервисов и других платформ. В настоящее время он живет недалеко от Сиэттла, штат Вашингтон.



26.10.2012

Об этом цикле статей

Вы думаете, что знаете о Java-программировании все? На самом деле большинство разработчиков только скребет по поверхности платформы Java, изучив ее лишь настолько, чтобы выполнять свою работу. В этом цикле статей Тед Ньюард углубляется в функциональные возможности платформы Java, выявляя малоизвестные факты, которые могут помочь в решении самых заковыристых задач программирования.

Сегодня многие Java-разработчики заинтересованы в использовании языков сценариев на платформе Java, но динамический язык не всегда можно скомпилировать в байт-код Java. В некоторых случаях быстрее и эффективнее просто написать сценарии отдельных частей Java-приложения или вызывать нужные объекты Java внутри сценария.

Вот здесь-то и приходит на помощь javax.script. API Java Scripting, введенный в Java 6, ликвидирует разрыв между удобными компактными языками сценариев и мощной экосистемой Java. Он позволяет быстро интегрировать с кодом Java практически любой язык сценариев, что открывает широкие возможности при решении мелких задач за чужой счет.

1. Выполнение JavaScript с помощью jrunscript

Развить навыки по этой теме

Этот материал — часть knowledge path для развития ваших навыков. Смотри Стать Java-программистом

Каждый новый выпуск платформы 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.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=842866
ArticleTitle=Пять секретов… API Java Scripting
publish-date=10262012