Том МакКвини
Java-разработчики знают, что Java не всегда является лучшим языком для решения некоторого рода задач. В этом году релизы версий 1.0 для JRuby и Groovy усилили интерес к встраиванию динамических языков в приложения Java. Groovy, JRuby, Rhino, Jython и прочие проекты с открытым кодом предоставляют возможность писать код на так называемых скриптовых языках и запускать его под управлением JVM. До сих пор интеграция таких языков с Java-кодом, обычно, подразумевала необходимость изучения для каждого интерпретатора его уникального API и особенностей реализации.
Пакет javax.script
, добавленный в Java SE 6, облегчает процесс интеграции динамических языков. Он предоставляет единообразный, простой способ вызова множества скриптовых языков при использовании небольшого набора интерфейсов и реальных классов. Но Java scripting API - это больше, чем просто облегчение в создании скриптовых фрагментов приложения; пакет поддержки скриптинга позволяет вам считывать и вызывать внешние скрипты на лету, что означает возможность динамической модификации самих скриптов для изменения поведения исполняемого приложения.
Эта статья, первая в серии из двух частей, является введением в возможности и ключевые классы Java scripting API с использованием приложения в стиле "Hello World". Часть 2 представляет более реалистичный пример приложения, раскрывающий дополнительные сильные стороны скриптового API. В этом приложении scripting API задействован для создания движка с динамически настраиваемыми правилами, в котором правила кодируются в виде внешних скриптов, написанных на Groovy, JavaScript и Ruby. Правила принимают решение - может ли соискатель внутреннего заема претендовать на отдельные ипотечные продукты. Воплощение таких правил посредством Java scripting API позволяет изменять сами правила и добавлять в процессе выполнения новые продукты ипотеки.
|
Скриптинг против динамики
Термин скриптовый обычно относится к языкам, запускаемым в интерпретирующей оболочке без выделения этапа копмиляции. Термин динамический , как правило, относится к языкам, ожидающим момента, когда среда выполнения определит тип переменной или поведение объекта и содержащим возможности на подобие замыканий ( closures ) и продлений ( continuations ). Некоторым языкам программирования общего назначения подходят оба термина. Предпочтение термину скриптовый язык здесь отдано по той причине, что эта статья сфокусирована на Java Scripting API, а не потому что упоминающимся языкам не достает динамических характеристик. | |
Пакет поддержки скриптинга был добавлен в язык Java в декабре 2006 для обеспечения унифицированного способа интеграции скриптовых языков в приложение Java. Для разработчиков языков пакет предоставляет способ написания связующего кода, позволяющего их языкам вызываться динамически из Java-приложения. Для Java-разработчиков пакет предлагает небольшой набор классов и интерфейсов, которые дают возможность скриптам, написанным на любом множестве языков, быть вызванным через обобщенный API. Пакет скриптинга, таким образом, подобен пакету Java Database Connectivity (JDBC) - с ним различные языки (как различные базы данных) могут быть интегрированы в платформу Java посредством согласующего интерфейса.
Ранее динамический вызов скриптового языка из Java-кода усложнялся использованием специфичных классов, поставляемых в дистрибутиве каждого языка, или же использованием Bean Scripting Framework (BSF) от Apache Jakarta. BSF объединяет небольшое количество скриптовых языков под управлением единого API. Более двух дюжин скриптовых языков, включая AppleScript, Groovy, JavaScript, Jelly, PHP, Python, Ruby и Velocity, могут быть интегрированы в Java-код при использовании Java SE 6 Scripting API, главным образом базирующемся на BSF.
Scripting API обеспечивает двухстороннюю видимость между Java-приложениями и внешними скриптами. Ваш Java-код может не только вызывать внешние скрипты, но также может предоставить таким скриптам доступ к избранным Java-объектам. Внешний скрипт Ruby, к примеру, может вызывать методы Java-объектов и иметь доступ к их свойствам, что позволяет скриптам добавлять в исполняемое приложение поведение, не предусмотренное на момент разработки.
Обращение к внешним скриптам может быть использовано для расширения функциональности приложения по ходу его выполнения, конфигурирования, мониторинга или других оперативных манипуляций - таких как изменение бизнес-логики без необходимости останавливать приложение. Возможные применения пакета поддержки скриптинга включают:
- Написание бизнес-логики на более простом, чем Java, языке, не обращаясь при этом к развитым средам выполнения.
- Построение архитектуры на базе подключаемых модулей (плагинов), позволяющей пользователям настраивать приложение на лету.
- Интегрировать существующий скрипт в ваше Java-приложение, скажем, скрипт обрабатывающий или преобразовывающий текстовые файлы.
- Производить внешнюю оперативную конфигурацию поведения приложения с помощью полноценного языка программирования вместо настроечного файла.
- Добавлять в Java-приложение язык, специфичный для данной предметной области.
- Применять скриптовый язык на этапе прототипирования Java-приложения.
- Писать на скриптовом языке тестирующий код для приложения.
Класс HelloScriptingWorld
, который вы можете загрузить наряду с прочим кодом для этой статьи (см. Файлы для загрузки), демонстрирует ключевые возможности пакета поддержки Java-скриптинга. В нем используются жестко закодированные фрагменты на JavaScript, взятого в качестве примера скриптового языка. Содержащийся в классе метод main()
, показанный в листинге 1, создает исполняющее окружение для JavaScript скрипта, а затем вызывает пять методов (показанных в последующих листингах), подчеркивающих возможности пакета:
Листинг 1. Метод main для HelloScriptingWorld
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
ScriptEngineManager scriptEngineMgr = new ScriptEngineManager();
ScriptEngine jsEngine = scriptEngineMgr.getEngineByName("JavaScript");
if (jsEngine == null) {
System.err.println("Для JavaScript не найдено скриптового движка");
System.exit(1);
}
System.out.println("Вызываем invokeHelloScript...");
invokeHelloScript(jsEngine);
System.out.println("\nВызываем defineScriptFunction...");
defineScriptFunction(jsEngine);
System.out.println("\nВызываем invokeScriptFunctionFromEngine...");
invokeScriptFunctionFromEngine(jsEngine);
System.out.println("\nВызываем invokeScriptFunctionFromJava...");
invokeScriptFunctionFromJava(jsEngine);
System.out.println("\nВызываем invokeJavaFromScriptFunction...");
invokeJavaFromScriptFunction(jsEngine);
}
|
Главная задача метода main()
- получение экземпляра javax.script.ScriptEngine
(две первых конструкции в листинге 1). Скриптовый движок загружает и выполняет скрипты для некоторого конкретного языка. Это наиболее часто используемый и востребованный класс в пакете Java-скриптинга. Вы извлекаете скриптовый движок из javax.script.ScriptEngineManager
(первое выражение присваивания). Типичная необходимость - получение в программе только одного экземпляра движка, если только не используется множество скриптовых языков.
ScriptEngineManager
, возможно, единственный реальный класс в пакете скриптинга к которому вы будете обращаться регулярно; большинство прочего - интерфейсы. И это, может быть, единственный класс из пакета скриптинга, экземпляры которого вы будете создавать напрямую (или косвенно - через механизм внедрения зависимости ( dependency-injection) так, как это делается в Spring Framework.) ScriptEngineManager
может возвращать скриптовый движок одним из трех способов:
- По имени движка или языка, как запрашивается
JavaScript
в листинге 1.
- По расширению файла, общепринятому в использовании для скриптов на этом языке, скажем, .rb для Ruby.
- По MIME-типу, в случае, когда в скриптовом движке заявлена его поддержка.
|
Почему в нашем примере - JavaScript?
В нашем примере Hello World мы используем JavaScript отчасти потому что его код прост для понимания, но в большей степени - потому что среды выполнения для Java 6 в поставке от Sun Microsystems и BEA Systems комплектуются JavaScript-интерпретатором на базе реализации с открытым кодом Mozilla Rhino. Для JavaScript у вас нет необходимости добавлять JAR-файлы поддержки скриптового языка в переменную окружения CLASSPATH . | |
ScriptEngineManager
-ы находят и создают скриптовые движки опосредованно. Т.е. при создании экземпляров ScriptEngine-менеджеров они обращаются к механизму поиска сервиса (добавлено в Java 6) для обнаружения всех зарегистрированных реализаций javax.script.ScriptEngineFactory
в CLASSPATH
Эти фабричные классы поставляются в пакетах с реализациями Java scripting API; вам, скорее всего, никогда не придется непосредственно иметь дело с этими классами.
После того, как ScriptEngineManager
нашел все фабричные классы соответствующих скриптовых движков, он опрашивает каждую из фабрик чтобы выяснить: можно ли создать движок требуемого типа - JavaScript для случая в листинге 1. Если фабрика отвечает утвердительно, менеджер просит фабрику создать движок и он будет возвращен потребителю. Менеджер возвращает null
если фабрика для целевого языка не найдена, что предусмотрено в коде листинга 1, в целях надежности проверяющем возвращаемое значение на null
.
Как я уже упоминал, ваш код использует экземпляр ScriptEngine
для выполнения скрипта. Скриптовый движок действует как посредник между вашим скриптовым кодом и целевым языковым интерпретатором или компилятором, который, в конечном счете, и исполняет код. Следовательно, вам не нужно знать какие классы используются каждым интерпретатором для отработки кода. Например, скриптовый движок для JRuby может сначала передать ваш код в экземпляр класса org.jruby.Ruby
для компиляции скрипта в некоторую промежуточную форму, затем вызвать его снова для прогона скрипта и обработки возвращаемых значений. Реализация скриптовой поддержки скрывает детали, включая то, как интерпретатор согласовывает определения классов, объекты приложения и потоки ввода/вывода с Java-кодом.
На рис. 1 в общем виде показаны взаимосвязи между вашим приложением, Java scripting API, реализацией ScriptEngine
и интерпретатором скриптового языка. Вы можете отметить, что ваше приложение опирается только на API скриптинга, предоставляемое классом ScriptEngineManager
и интерфейсом ScriptEngine
. Компонент реализации интерфейса ScriptEngine
обслуживает всю специфику использования конкретного языкового интерпретатора.
Рисунок 1: Взаимосвязи компонентов Scripting API
Вас, надо полагать, заинтересует вопрос где же взять необходимые JAR-файлы, имплементирующие скриптовый движок и языковый интерпретатор. Наилучшим местом для поиска реализации движка является, прежде всего, проект с открытым кодом Scripting, поддерживаемый java.net (см. Ресурсы). Здесь вы найдете реализации скриптовых движков для многих языков и ссылки на прочие ресурсы по теме. Проект Scripting также предоставляет ссылки для загрузки интерпретаторов поддерживаемых скриптовых языков.
В листинге 1 метод main()
передает ScriptEngine
в каждый метод для отработки соответствующего JavaScript-кода. Первый метод приведен в листинге 2. Метод invokeHelloScript()
вызывает на стороне движка метод eval
для вычисления и выполнения переданной строки кода на JavaScript. Интерфейс ScriptEngine
определяет шесть перегруженных методов eval()
, принимающих скрипт к обработке как в виде строки, так и через объект java.io.Reader
, широко используемый для вычитывания скриптов из внешних источников, таких как файлы.
Листинг 2. Метод invokeHelloScript
private static void invokeHelloScript(ScriptEngine jsEngine) throws ScriptException {
jsEngine.eval("println('Hello from JavaScript')");
}
|
|
Контекст выполнения скрипта
Пример скрипта в приложении HelloScriptingWorld осуществляет вывод в консоль используя JavaScript-функцию println() , но у вас есть полный контроль над потоками ввода и вывода. Скриптовые движки предоставляют опцию для изменения контекста выполнения скрипта, т.е. вы можете переназначать потоки, закрепленные за standard input, standard output и standard error, а также определять - какие глобальные переменные и объекты Java доступны выполняемому скрипту. | |
JavaScript в методе invokeHelloScript()
выводит Hello from JavaScript
в поток стандартного вывода (standard output), являющийся, в данном случае, консольным окном. (листинг 6 содержит окончательный вывод после запуска HelloScriptingWorldApplication
.)
Обратите внимание - этот и другие методы класса декларируют, что они выбрасывают исключение javax.script.ScriptException
. Это проверяемое исключение - единственное, определенное в пакете скриптинга - указывает на то, что движок потерпел неудачу при синтаксическом разборе или выполнении кода. Все методы eval()
скриптового движка выбрасывают ScriptException
, поэтому в коде вам необходимо соответствующим образом обработать эту ситуацию.
В листинге 3 показаны два связанных метода: defineScriptFunction()
и invokeScriptFunctionFromEngine()
. Метод defineScriptFunction()
также вызывает на движке метод eval()
с явно заданным фрагментом JavaScript-кода. Однако отметьте - этот метод всего лишь задает определение JavaScript-функции sayHello()
. Выполнения кода не происходит. Функция sayHello()
принимает один параметр, который затем выводится в консоль следующим далее вызовом println()
. JavaScript-интерпретатор скриптового движка добавляет эту функцию в свое глобальное окружение, делая ее доступной в последующих вызовах eval
, что происходит (и это не удивительно) в методе invokeScriptFunctionFromEngine()
.
Листинг 3. Методы defineScriptFunction и invokeScriptFunctionFromEngine
private static void defineScriptFunction(ScriptEngine engine) throws ScriptException {
// Define a function in the script engine
engine.eval(
"function sayHello(name) {" +
" println('Hello, ' + name)" +
"}"
);
}
private static void invokeScriptFunctionFromEngine(ScriptEngine engine)
throws ScriptException
{
engine.eval("sayHello('World!')");
}
|
Эта пара методов демонстрирует, что скриптовые движки могут сохранять состояние компонентов приложения и делать это состояние доступным на протяжении последующих вызовов методов eval()
. Метод invokeScriptFunctionFromEngine()
использует преимущество, предоставленное сохраненным состоянием и вызывает JavaScript-функцию, определенную в предыдущем вызове eval()
.
Итак, многие скриптовые движки сохраняют состояние глобальных переменных и функций между вызовами eval()
. Однако, и это важно учитывать, Java Scripting API не требует от движков поддержки такой возможности. Скриптовые движки JavaScript, Groovy и JRuby, используемые в этой статье, обеспечивают сохранность состояния между вызовами eval()
.
Листинг 4 является вариацией предыдущего примера. Метод invokeScriptFunctionFromJava()
отличается тем, что вызывает JavaScript-функцию sayHello()
не прибегая к методу eval()
, принадлежащему ScriptEngine
или к JavaScript-коду. Вместо этого он использует интерфейс javax.script.Invocable
из состава Java Scripting API для вызова функции, поддерживаемой скриптовым движком. Метод invokeScriptFunctionFromJava()
приводит объект скриптового движка к интерфейсному типу Invocable
, а затем обращается к интерфейсному методу invokeFunction()
для вызова JavaScript-функции sayHello()
с заданным параметром. Если вызываемая функция возвращает значение, метод invokeFunction()
вернет его, упаковав в Java-тип Object
.
Листинг 4. Метод invokeScriptFunctionFromJava
private static void invokeScriptFunctionFromJava(ScriptEngine engine)
throws ScriptException, NoSuchMethodException
{
Invocable invocableEngine = (Invocable) engine;
invocableEngine.invokeFunction("sayHello", "from Java");
}
|
|
Продвинутый вызов скриптов с использованием прокси
Если скриптовая функция или метод реализуют Java-интерфейс - доступно более развитое использование Invocable . Интерфейс Invocable определяет метод getInterface() , принимающий в качестве параметра интерфейс и возвращающий прокси-объект Java, реализующий этот предоставленный ранее интерфейс. Как только вы получили прокси-объект из скриптового движка, вы можете рассматривать его как обыкновенный Java-объект. Вызываемые на прокси методы делегируются в скриптовый движок для выполнения скриптовым языком. | |
Отметьте также, что листинг 4 не содержит JavaScript. Интерфейс Invocable
позволяет Java-коду вызывать скриптовую функцию не зная языка ее реализации. Метод invokeFunction()
выбрасывает java.lang.NoSuchMethodException
если скриптовый движок не смог найти функцию с данным именем или типом параметров.
Java Scripting API не требует от скриптового движка имплементации интерфейса Invocable
. На самом деле, код в листинге 4 должен был бы перед приведением типа задействовать оператор instanceof
, чтобы убедиться в том, что движок реализует интерфейс Invocable
.
Вызов Java-методов из скриптового кода
Примеры в листинге 3 и листинге 4 показывают как Java-код может вызывать функции или методы, определенные в скриптовом языке. Теперь же вас, вероятно, заинтересует - может ли код скриптового языка, в свою очередь, вызывать методы Java-объектов. Может. Метод invokeJavaFromScriptFunction()
в листинге 5 показывает - как получить доступ к Java-объектам со стороны скриптового движка и как скриптовый код может вызывать методы этих Java-объектов. В частности, метод invokeJavaFromScriptFunction()
использует предоставляемый движком метод put()
для передачи экземпляра того же класса HelloScriptingWorld
в движок. После того, как движок получил доступ к Java-объекту через имя, переданное при вызове put()
, скриптовый код вызовом метода eval()
использует его.
Листинг 5. Методы invokeJavaFromScriptFunction и getHelloReply
private static void invokeJavaFromScriptFunction(ScriptEngine engine)
throws ScriptException
{
engine.put("helloScriptingWorld", new HelloScriptingWorld());
engine.eval(
"println('Вызываем метод getHelloReply из JavaScript...');" +
"var msg = helloScriptingWorld.getHelloReply(vJavaScript');" +
"println('Получили из Java: ' + msg)"
);
}
/** Метод, вызываемый из вышеприведенного скрипта и возвращающий строку. */
public String getHelloReply(String name) {
return "Java-метод getHelloReply говорит 'Привет, " + name + "'";
}
|
JavaScript-код, содержащийся в вызове метода eval()
из листинга 5 использует Java-объект HelloScriptingWorld
посредством доступа к нему через переменную с именем helloScriptingWorld
, переданным в вызов метода put()
на стороне скриптового движка. Вторая строка JavaScript-кода вызывает публичный Java-метод getHelloReply()
, также приведенный в листинге 5. Метод getHelloReply()
возвращает строку Java-метод getHelloReply говорит 'Привет, <параметр>'
. Код JavaScript в методе eval()
присваивает возвращаемое из Java значение переменной msg
, затем выводит это значение в консоль.
|
Портирование Java-объектов
Когда скриптовый движок делает 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, чтобы облегчить компиляцию и запуск примера приложения. Выполните следующие шаги:
- Скачайте zip-архив.
- Создайте новый каталог, скажем, java-scripting и распакуйте сюда полученный на предыдущем шаге архив.
- Откройте командное окно и перейдите в этот каталог.
- Запустите
ant run-hello
.
Вы должны увидеть консольный вывод из Ant, сходный с приведенным в листинге 6. Обратите внимание на то, что метод defineScriptFunction()
не генерирует вывода, т.к. он определяет, но не вызывает JavaScript-функцию.
Листинг 6. Вывод из запущенного HelloScriptingWorld
Вызываю invokeHelloScript...
Hello from JavaScript
Вызываю defineScriptFunction...
Вызываю invokeScriptFunctionFromEngine...
Hello, World!
Вызываю invokeScriptFunctionFromJava...
Hello, from Java
Вызываю invokeJavaFromScriptFunction...
Вызов метода getHelloReply из JavaScript...
Получили из Java: Java-метод getHelloReply говорит 'Привет, JavaScript'
|
Java Scripting API появился в Java SE 6, но вы также можете использовать его и с Java SE 5. Вам всего лишь необходимо предоставить реализацию недостающих классов из пакета javax.script
. К счастью, реализация доступна из Java Specification Request 223 reference implementation. JSR 223 определяет Java scripting API.
Если вы загрузите ссылочную реализацию JSR 223, распакуйте архив и поместите файлы script-api.jar, script-js.jar и js.jar в месте, доступном по classpath. Эти файлы из комплекта поставки Java SE 6 воплощают скриптовый API, интерфейс скриптового движка JavaScript и сам скриптовый движок JavaScript.
За тем, как вы передаете 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-код призван помочь вам разобраться с этими концепциями.
Теперь, когда вы располагаете базовыми знаниями по Java Scripting API, Часть 2 этой статьи улучшит и расширит эти знания при помощи более реалистичного примера приложения. Это приложение использует комбинацию внешних скриптовых файлов, написанных на Groovy, Ruby и JavaScript для задания бизнес-логики с возможностью ее оперативного изменения. Как вы увидите, определение бизнес-правил в скриптовом языке облегчает написание этих правил и, возможно, упрощает восприятие при чтении непрограммистами, такими как бизнес-аналитики или специалисты по описанию предметной области.
Файлы для загрузки