Використовуйте динамічні мови динамічно частина 1

Java-розробники знають, що Java не завжди є кращим мовою для вирішення певного роду завдань. Цього року релізи версій 1.0 для JRuby і Groovy посилили інтерес до вбудовування динамічних мов в додатки Java. Groovy, JRuby, Rhino, Jython та інші проекти з відкритим кодом надають можливість писати код на так званих скриптових мовах і запускати його під керуванням JVM. До сих пір інтеграція таких мов з Java-кодом, зазвичай, мала на увазі необхідність вивчення для кожного інтерпретатора його унікального API і особливостей реалізації.

Пакет javax.script. доданий в Java SE 6, полегшує процес інтеграції динамічних мов. Він надає однаковий, простий спосіб виклику безлічі скриптових мов при використанні невеликого набору інтерфейсів і реальних класів. Але Java scripting API - це більше, ніж просто полегшення в створенні скриптових фрагментів додатки; пакет підтримки скриптинга дозволяє вам зчитувати і викликати зовнішні скрипти на льоту, що означає можливість динамічної модифікації самих скриптів для зміни поведінки виконуваного додатка.

Скриптинг проти динаміки

Scripting API забезпечує двосторонню видимість між Java-додатками і зовнішніми скриптами. Ваш Java-код може не тільки викликати зовнішні скрипти, але також може надати таким скриптів доступ до свого улюбленого Java-об'єктів. Зовнішній скрипт Ruby, наприклад, може викликати методи Java-об'єктів і мати доступ до їх властивостями, що дозволяє скриптам додавати в який виконувався додаток поведінку, не передбачене на момент розробки.

Звернення до зовнішніх скриптів може бути використано для розширення функціональності програми по ходу його виконання, конфігурації, моніторингу або інших оперативних маніпуляцій - таких як зміна бізнес-логіки без необхідності зупиняти додаток. Можливі застосування пакету підтримки скриптинга включають:

  • Написання бізнес-логіки на простішому, ніж Java, мовою, не звертаючись при цьому до розвинених середах виконання.
  • Побудова архітектури на базі модулів (плагінів), що дозволяє користувачам налаштовувати додаток на льоту.
  • Інтегрувати існуючий скрипт в ваше Java-додаток, скажімо, скрипт обробляє або перетворюють текстові файли.
  • Виробляти зовнішню оперативну конфігурацію поведінки програми за допомогою повноцінного мови програмування замість настроечного файлу.
  • Додавати в Java-додаток мова, специфічний для даної предметної області.
  • Застосовувати скриптова мова на етапі прототипирования Java-додатка.
  • Писати на скриптовій мовою тестує код для додатка.

Лістинг 1. Метод main для HelloScriptingWorld

Головне завдання методу main () - отримання примірника javax.script.ScriptEngine (дві перших конструкції в лістингу 1). Скриптова движок завантажує та виконує скрипти для деякого конкретного мови. Це найбільш часто використовуваний і затребуваний клас в пакеті Java-скриптинга. Ви витягаєте скриптова движок з javax.script.ScriptEngineManager (перший вираз присвоювання). Типова необхідність - отримання в програмі тільки одного примірника движка, якщо тільки не використовується безліч скриптових мов.

ScriptEngineManager. можливо, єдиний реальний клас в пакеті скриптинга до якого ви будете звертатися регулярно; більшість іншого - інтерфейси. І це, може бути, єдиний клас з пакета скриптинга, екземпляри якого ви будете створювати безпосередньо (або опосередковано - через механізм впровадження залежності (dependency-injection) так, як це робиться в Spring Framework.) ScriptEngineManager може повертати скриптова движок одним з трьох способів :

ScriptEngineManager -и знаходять і створюють скріптові движки опосередковано. Тобто при створенні екземплярів ScriptEngine-менеджерів вони звертаються до механізму пошуку сервісу (додано в Java 6) для виявлення всіх зареєстрованих реалізацій javax.script.ScriptEngineFactory в CLASSPATH Ці фабричні класи поставляються в пакетах з реалізаціями Java scripting API; вам, швидше за все, ніколи не доведеться безпосередньо мати справу з цими класами.

Як я вже згадував, ваш код використовує екземпляр ScriptEngine для виконання скрипта. Скриптова движок діє як посередник між вашим скриптовою кодом і цільовим мовним інтерпретатором або компілятором, який, в кінцевому рахунку, і виконує код. Отже, вам не потрібно знати які класи використовуються кожним інтерпретатором для відпрацювання коду. Наприклад, скриптова движок для JRuby може спочатку передати ваш код в екземпляр класу org.jruby.Ruby для компіляції скрипта в деяку проміжну форму, потім викликати його знову для прогону скрипта і обробки значень, що повертаються. Реалізація скриптовой підтримки приховує деталі, включаючи те, як інтерпретатор погоджує визначення класів, об'єкти додатка і потоки введення / виводу з Java-кодом.

На рис. 1 в загальному вигляді показані взаємозв'язку між вашим додатком, Java scripting API, реалізацією ScriptEngine і інтерпретатором скриптового мови. Ви можете відзначити, що ваш додаток спирається тільки на API скриптинга, що надається класом ScriptEngineManager і інтерфейсом ScriptEngine. Компонент реалізації інтерфейсу ScriptEngine обслуговує всю специфіку використання конкретного мовного інтерпретатора.

Малюнок 1: Взаємозв'язки компонентів Scripting API

Вас, мабуть, зацікавить питання де ж взяти необхідні JAR-файли, імплементує скриптова движок і мовний інтерпретатор. Найкращим місцем для пошуку реалізації движка є, перш за все, проект з відкритим кодом Scripting, підтримуваний java.net (див. Ресурси). Тут ви знайдете реалізації скриптових движків для багатьох мов і посилання на інші ресурси по темі. Проект Scripting також надає посилання для завантаження інтерпретаторів підтримуваних скриптових мов.

Лістинг 2. Метод invokeHelloScript

Контекст виконання скрипта

Зверніть увагу - цей та інші методи класу декларують, що вони викидають виняток javax.script.ScriptException. Це перевіряється виключення - єдине, певне в пакеті скриптинга - вказує на те, що движок зазнав невдачі при синтаксичному розборі або виконанні коду. Всі методи eval () скриптового движка викидають ScriptException. тому в коді вам необхідно відповідним чином обробити цю ситуацію.

Лістинг 3. Методи defineScriptFunction і invokeScriptFunctionFromEngine

Лістинг 4. Метод invokeScriptFunctionFromJava

Високий рівень виклик скриптів з використанням проксі

Якщо скриптова функція або метод реалізують Java-інтерфейс - є більш розвинене використання Invocable. Інтерфейс Invocable визначає метод getInterface (). приймає як параметр інтерфейс і повертає проксі-об'єкт Java, який реалізує цей наданий раніше інтерфейс. Як тільки ви отримали проксі-об'єкт з скриптового движка, ви можете розглядати його як звичайний Java-об'єкт. Викликані на проксі методи делегуються в скриптова движок для виконання скриптовою мовою.

Java Scripting API не вимагає від скриптового движка імплементації інтерфейсу Invocable. Насправді, код в лістингу 4 мав би перед приведенням типу задіяти оператор instanceof. щоб переконатися в тому, що движок реалізує інтерфейс Invocable.

Приклади в лістингу 3 та лістингу 4 показують як Java-код може викликати функції або методи, визначені в скриптовій мовою. Тепер же вас, ймовірно, зацікавить - чи може код скриптового мови, в свою чергу, викликати методи Java-об'єктів. Може. Метод invokeJavaFromScriptFunction () в лістингу 5 показує - як отримати доступ до Java-об'єктів з боку скриптового движка і як скриптова код може викликати методи цих Java-об'єктів. Зокрема, метод invokeJavaFromScriptFunction () використовує наданий движком метод put () для передачі примірника того ж класу HelloScriptingWorld в движок. Після того, як двигун отримав доступ до Java-об'єкту через ім'я, передане при виклику put (). скриптова код викликом методу eval () використовує його.

Лістинг 5. Методи invokeJavaFromScriptFunction і getHelloReply

Коли скриптова движок робить Java-об'єкт доступним скрипту, запущеного в виконуючою середовищі, движок повинен упакувати його в об'єктний тип, відповідний поточному скриптова мова. Така упаковка повинна виконувати відповідні перетворення об'єкт-значення, наприклад, допускати використання Java-об'єкта Integer безпосередньо в математичних виразах скриптового мови. З'ясування того, як Java-об'єкти портируют в скріптові об'єкти специфічно для кожного скриптового движка і виходить за рамки обговорюваного в цій статті. І все ж ви повинні усвідомлювати, що така трансляція відбувається, тому ви можете проводити тестування використовуваного скриптового мови, щоб переконатися в передбачуваності виконуваних перетворень.

ScriptEngine.put і пов'язаний з ним метод get () є основними способами розподілу доступу до об'єктів і даними між Java-кодом і виконуваними движком скриптами. (Розширене обговорення цієї теми див. Нижче в Область видимості виконуваного скрипта.) Коли ви викликаєте на движку метод put (). скриптова движок асоціює другий параметр (довільний Java-об'єкт) з заданим строковим ключем. Більшість скриптових движків забезпечують доступність цих Java-об'єктів в скриптах за допомогою заданого імені змінної. Двигуни вільні в поводженні з переданими вами в метод put () іменами. Наприклад, скриптова движок JRuby робить helloScriptingWorld доступною в Ruby-коді у вигляді глобальної змінної $ helloScriptingWorld. що відповідає синтаксису Ruby для глобальних змінних.

Метод get () движка витягує значення, доступні в скриптовій оточенні. У загальному випадку, кожна глобальна змінна і функція з оточення доступні в Java-коді через метод get (). Але для скриптів доступні тільки ті об'єкти Java, які заявлені скриптовими движку безпосередньо - викликом put ().

Така можливість доступу і маніпулювання Java-об'єктами в виконуваному додатку з боку зовнішніх скриптів є потужною технікою розширення функціональності ваших Java-програм. (Ця техніка задіяна в прикладі з Частини 2)

Ви можете запустити додаток HelloScriptingWorld завантаживши та скомпонувавши вихідний код. zip-файл містить компонувальні сценарії як для Ant так і для Maven, щоб полегшити компіляцію і запуск прикладу програми. Виконайте наступні кроки:

  1. Скачайте zip-архів.
  2. Створіть новий каталог, скажімо, java-scripting і розпакуйте сюди отриманий на попередньому кроці архів.
  3. Відкрийте командне вікно і перейдіть в цей каталог.
  4. Запустіть ant run-hello.

Лістинг 6. Висновок з запущеного HelloScriptingWorld

Java Scripting API з'явився в Java SE 6, але ви також можете використовувати його і з Java SE 5. Вам всього лише необхідно надати реалізацію відсутніх класів з пакета javax.script. На щастя, реалізація доступна з Java Specification Request 223 reference implementation. JSR 223 визначає Java scripting API.

За тим, як ви передаєте Java-об'єкти в виконувані движком скрипти, варто більш розвинена реалізація, ніж просто виклик движкових методів get () і put (). Коли ви викликаєте get () або put () на движку, він витягує або зберігає необхідний ключ в спеціально передбаченому екземплярі інтерфейсу javax.script.Bindings. (Інтерфейс Bindings це просто інтерфейс Map. Обслуговуючий рядкові ключі.)

Коли ваш код викликає моторний метод eval (). на стороні движка використовується зумовлене зв'язування ключів зі значеннями. Однак, ви можете надати свій власний об'єкт Bindings для обслуговування викликів eval (). щоб обмежити видимість змінних і об'єктів для даного скрипта. Тоді виклик буде виглядати як eval (String, Bindings) або eval (Reader, Bindings). Щоб полегшити створення ваших специфічних Bindings. скріптові движки пропонують метод createBindings (). повертає порожній об'єкт Bindings. Виклик eval на об'єкті Bindings тимчасово приховує Java-об'єкти, збережені раніше з використанням визначеного в движку зв'язування.

Для накопичення історії скриптова движок має в своєму складі два зумовлених механізму зв'язування: зв'язування з областю видимості на рівні движка (engine scope bindings) використовуються при викликах get () і put (). а зв'язування з глобальної областю видимості (global scope bindings) движок може застосовувати для пошуку об'єктів в разі, якщо їх не вдалося виявити на рівні зв'язування "engine scope". Формулювання може - істотна. Скриптові движки не зобов'язані забезпечувати доступність глобального зв'язування для скриптів. Хоча багато скріптові движки такий доступ надають.

Конструктивне призначення "global scope" -связиванія - спільне використання об'єктів різними скриптовими двигунами. Кожен движок, що повертається екземпляром ScriptEngineManager. комплектується одним і тим же об'єктом глобального зв'язування. Ви можете отримати цей об'єкт викликом методу getBindings (ScriptContext.GLOBAL_SCOPE) і призначити об'єкт глобального зв'язування для движка за допомогою setBindings (Bindings, ScriptContext.GLOBAL_SCOPE).

ScriptContext - це інтерфейс, який визначає і управляє контекстом часу виконання скриптового движка. ScriptContext містить зв'язування з "моторної" і "глобальної" областями видимості, а також потоки введення / виводу, що використовуються движком для стандартних операцій введення / виводу. Ви можете отримати контекст скриптового движка і маніпулювати ним за допомогою моторного методу getContext ().

Концепції Scripting API, такі як область видимості scope. зв'язування bindings і контекст можуть, спочатку, збивати з пантелику, через своїх частково перекриваються смислів. Завантажувальний файл з вихідними кодами для цієї статті включає тестовий файл JUnit, який має назву ScriptApiRhinoTest і розташований в каталозі src / test / java. Що міститься там Java-код покликаний допомогти вам розібратися з цими концепціями.

Файли для завантаження

Схожі статті