Мы приближаемся к концу первой части книги, где описываются основы Java_ Script. Вторая часть этой книги полностью посвящена использованию Java_ Script в веб_броузерах. Однако прежде чем приступить к обсуждению этой темы,
1 Эта глава предназначена для Java_программистов, и многие примеры в ней напи_ саны целиком или частично на языке Java. Если вы не знакомы с этим языком программирования, можете просто пропустить эту главу.
230 Глава 12. Разработка сценариев для Java*приложений
коротко рассмотрим вопрос встраивания JavaScript в другие приложения. Необ_ ходимость встраивания JavaScript в приложения обычно диктуется стремлением дать пользователю возможность подстраивать приложение под свои нужды с по_ мощью сценариев. Веб_броузер Firefox, например, позволяет управлять пользо_ вательским интерфейсом с помощью JavaScript_сценариев. Многие другие при_ ложения, обладающие широчайшими возможностями, поддерживают настрой_ ку с помощью языков сценариев того или иного вида.
В рамках проекта Mozilla реализовано два интерпретатора JavaScript, распро_ страняемых с открытыми исходными текстами. Интерпретатор SpiderMonkey – оригинальная версия JavaScript, реализованная на языке C. Версия Rhino реа_ лизована на языке Java. Обе версии имеют прикладной интерфейс для встраива_ ния. Если возникнет необходимость добавить возможность управлять с помощью JavaScript_сценариев приложением, написанным на языке С, выбирайте версию SpiderMonkey. В случае необходимости добавить возможность управлять Java_ приложением с помощью сценариев, следует выбрать версию Rhino. Подробнее узнать об использовании этих интерпретаторов в своих приложениях можно по адресам http://www.mozilla.org/js/spidermonkey и http://www.mozilla.org/rhino.
С появлением Java 6.0 стало еще проще ввести поддержку JavaScript_сценариев в Java_приложения. Именно эта тема и является предметом обсуждения данной главы. В составе Java 6 появились новый пакет javax.script, реализующий обоб_ щенный интерфейс для подключения языков сценариев, и встроенная версия ин_ терпретатора JavaScript – Rhino, которая использует этот пакет в своей работе.1
В примере 12.1 демонстрируются основы использования пакета javax.script: в этом примере создаются объект ScriptEngine, который представляет собой эк_ земпляр интерпретатора JavaScript, и объект Bindings, хранящий значения Java_ Script_переменных. После этого запускается сценарий, хранящийся во внешнем файле, за счет передачи объекта_потока java.io.Reader и связующего объекта Bindings методу eval() объекта ScriptEngine. Метод eval() возвращает результат работы сценария или генерирует исключение ScriptException, если в процессе ис_ полнения сценария возникла ошибка.
Пример 12.1. Программа на языке Java, запускающая JavaScript'сценарии
import javax.script.*; import java.io.*;
// Запускает файл JavaScript_сценария и выводит результаты его работы public class RunScript {
public static void main(String[] args) throws IOException {
// Создать экземпляр интерпретатора, или "ScriptEngine", для запуска сценария. ScriptEngineManager scriptManager = new ScriptEngineManager();
1 К моменту написания этих строк реализация Java 6 еще находилась в стадии раз_ работки. Пакет javax.script уже в достаточной степени проработан, чтобы его мож_ но было здесь описывать, однако есть некоторая вероятность, что прикладной ин_ терфейс пакета может претерпеть какие_либо изменения в окончательной версии.
12.1. Встраивание JavaScript
// Объект Bindings – это таблица символов, или пространство имен,
// для интерпретатора. Он хранит имена и значения переменных
// и делает их доступными в сценарии. Bindings bindings = js.createBindings( );
// Обработка аргументов. Строка может содержать произвольное число аргументов
// вида _Dname=value, которые определяют переменные для использования в сценарии.
// Любые аргументы, начинающиеся не с –D, воспринимаются как имена файлов for(int i = 0; i < args.length; i++) {
String arg = args[i];
if (arg.startsWith("_D")) {
int pos = arg.indexOf('='); if (pos == _1) usage();
String name = arg.substring(2, pos); String value = arg.substring(pos+1);
// Обратите внимание: все объявляемые переменные являются строковыми.
// Сценарии могут преобразовывать их в другие типы по мере необходимости.
// Кроме того, существует возможность передавать java.lang.Number,
// java.lang.Boolean и любые другие объекты Java или значение null. bindings.put(name, value);
}
else {
if (filename != null) usage();// файл должен быть единственным filename = arg;
}
}
// Убедиться, что строка аргументов содержала имя файла. if (filename == null) usage();
// Добавить одну или более связок с помощью специальной
// зарезервированной переменной, чтобы передать интерпретатору имя
// файла сценария, который следует исполнить.
// Это позволит получать более информативные сообщения об ошибках. bindings.put(ScriptEngine.FILENAME, filename);
// Создать объект_поток для чтения файла сценария.
Reader in = new FileReader(filename);
try {
// Выполнить сценарий, используя объекты с переменными, и получить результат. Object result = js.eval(in, bindings);
// Вывести результат. System.out.println(result);
}
catch(ScriptException ex) {
// Или вывести сообщение об ошибке. System.out.println(ex);
232 Глава 12. Разработка сценариев для Java*приложений
Объект Bindings, создаваемый в этом примере, не является статическим – любые переменные, создаваемые JavaScript_сценарием, хранятся в этом объекте. В при_ мере 12.2 приводится более практичный пример на языке Java. Здесь объект Bindings сохраняется в объекте ScriptContext на более высоком уровне области ви_ димости, что позволяет обращаться к переменным, но новые переменные в объ_ екте Bindings не сохраняются. Пример представляет собой реализацию простей_ шего класса обработки файлов с возможностью настройки: параметры хранятся в текстовом файле в виде пар имя/значение, получить которые можно с помо_ щью описываемого здесь класса Configuration. Значения могут быть строковы_ ми, числовыми или логическими, а если значение окружено фигурными скобка_ ми, оно передается интерпретатору JavaScript для вычисления. Интересно, как объект java.util.Map, хранящий пары имя/значение, обернут в объект SimpleBin_ dings, благодаря чему интерпретатор JavaScript может также обращаться к зна_ чениям других переменных, определенных в том же самом файле.1
Пример 12.2. Класс обработки файлов с возможностью настройки, который интерпретирует JavaScript'выражения
* Этот класс напоминает java.util.Properties, но позволяет определять
* значения свойств в виде выражений на языке JavaScript.
*/
public class Configuration {
// Здесь по умолчанию будут храниться пары имя/значение Map<String,Object> defaults = new HashMap<String,Object>( );
// Методы доступа к значениям параметров
public Object get(String key) { return defaults.get(key); }
public void put(String key, Object value) { defaults.put(key, value); }
// Инициализировать содержимое объекта Map из файла с парами имя/значение.
// Если значение окружено фигурными скобками, оно должно вычисляться
// как выражение на языке JavaScript.
public void load(String filename) throws IOException, ScriptException { // Создать экземпляр интерпретатора
ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByExtension("js");
// Использовать собственные пары имя/значение в качестве JavaScript_переменных. Bindings bindings = new SimpleBindings(defaults);
// Создать контекст исполнения сценариев.
ScriptContext context = new SimpleScriptContext();
1 Как будет показано далее в этой же главе, сценарию на языке JavaScript доступ_ ны любые общедоступные члены любых общедоступных классов. Поэтому из со_ ображений безопасности программный Java_код, запускающий пользовательские сценарии, обычно исполняется с ограниченными привилегиями. Однако обсуж_ дение системы безопасности Java выходит за рамки темы этой книги.
12.1. Встраивание JavaScript
// Определить контекст переменных, чтобы они были доступны из сценария,
// но чтобы переменные, создаваемые в сценарии, не попадали в объект Map context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
BufferedReader in = new BufferedReader(new FileReader(filename));
String
line;
while((line
=
in.readLine( )) != null) {
line = line.trim( ); // отбросить ведущие и завершающие пробелы