Перейти к содержимому

Как из groovy скрипта вызвать java метод

  • автор:

Определение метода внутри Groovy, используемого в Java

Я использую Интерпретатор Groovy в Java class , я пытаюсь определить метод и вызвать его, вот мой код:

 Binding binding = new Binding(); binding.setVariable("aa", 1); binding.setVariable("bb", 2); GroovyShell shell = new GroovyShell(binding); shell.evaluate("int add(int a,int b)"); int value =(int) shell.evaluate("add(aa,bb);"); System.out.println(value); 

И у меня возникла эта ошибка:

Исключение в потоке «main» groovy.lang.MissingMethodException: Нет сигнатуры метода: Script2.add() применим к типам аргументов: (java.lang.Integer, java.lang.Integer) значения: [1, 2] Возможные решения: any(), wait(), run(), run(), find(), wait(long, int)

Пожалуйста, подскажите, как определить функцию внутри groovy и почему мой код не работает.

Лучшее для этого,

Поделиться Источник 05 ноября 2017 в 10:15

1 ответ

Когда вы используете GroovyShell#evaluate ,, оболочка компилирует весь скрипт в класс JVM, загружает его и запускает, а затем возвращает результат. Этот процесс независим для каждого вызова evaluate (обратите внимание, что имя класса — Script2 ; метод был определен в Script1 ), поэтому ваш метод не доступен во втором вызове.

Варианты для работы вокруг этого включают простое объединение строк перед оценкой (это будет работать для ваших собственных скриптов, но может возникнуть проблемы, если что-то в скриптах вызывает return ) или использует более сложный GroovyScriptEngine .

Groovy как скриптовый язык и DSL для Java

При разработке разного рода ПО, например, задач мониторинга или обслуживания, я сталкиваюсь с необходимостью поддержки в программе выполнения сценариев, описывающих или выполняющих некоторый набор действий. И специфика такова, что добавление или изменение таких сценариев в ПО не должно требовать пересборки или перезапуска ПО.

Наверное, самыми простыми примерами таких сценариев, с которыми все сталкивались в том или ином виде, могут служить обычные пакетные файлы — bat или sh.

В своей практике, я иногда использую XML для описания сценариев. В случае, четко определенного заранее набора действий и их простого плоского набора без ветвлений — XML не плох для использования. Тут и фиксированная структура, позволяющая валидировать файл сценария, и простота самого языка XML, позволяющая с ним работать не только программистам. Однако реализовывать ветвления или условную логику в XML, на мой взгляд, очень накладно и сродни написанию своего мини языка. Расширение набора действий чаще всего возможно только при изменении исходного кода программы, да и изначально реализация поддержки XML сценария трудозатратна.

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

Как?
Скрипты

Я не буду рассказывать основы Groovy, так как материалов в сети даже на русском уже предостаточно. Остановлюсь лишь на некоторых ключевых для нас моментах.

Groovy позволяет нам из Java кода выполнять не скомпилированный исходный Groovy код, что позволяет нам выполнять сценарии добавленные или измененные в runtime.

Рассмотрим пример выполнения Groovy скрипта в Java. Для поддержки Groovy в Вашем Java проекте нужно подключить в зависимости лишь одну библиотеку «groovy» нужной Вам версии.

Напишем следующий Groovy код в файле x:\GroovyScript.groovy:

println «Groovy script»
multi = <
num1, num2 -> num1 * num2
>
multi ( 4 , 4 )

Код для выполнения данного скрипта в Вашем Java код может быть таким:

GroovyShell shell = new GroovyShell ( ) ;
Object result = shell. evaluate ( new File ( «x:/GroovyScript.groovy» ) ) ;
System . out . println ( «result #66cc66″>+ result ) ;

В итоге выполнения, в консоль выведется 2 строки, первая из скрипта, вторая — из Java, с результатом выполнения скрипта:

Groovy script
result=16

Пример не несет функциональной нагрузки в скрипте, однако показывает возможности динамической загрузки и выполнения – всего две строчки кода и мы получаем возможность выполнения скриптов.

Немного о Java коде. GroovyShell – это класс предоставляемый Groovy для выполнения groovy скриптов. Существуют и другие способы выполнения groovy скриптов, подробнее смотрите тут

DSL

DSL – domain-specific language или предметно ориентированный язык. Язык, позволяющий использовать основные операции предметной области через набор простых и понятных высокоуровневых функций, скрывающий от пользователя их реализацию.

В приведенном выше примере код достаточно простой, однако в реальном сценарии он может быть очень большим и сложным. И работать с такими скриптами смогут только groovy-разработчики, избежать ошибок без тестирования будет сложно. В случае заранее известных операций в сценариях, всю бизнес логику можно вынести в код (java или groovy — неважно), и предоставить возможность использовать ее через набор функций.

Рассмотрим небольшой пример. Требуется написать скрипт, который будет выполнять архиваций, распаковку архивов, удаление, некоторые проверки и уведомления.
Один их кусочков сценария мог бы быть таким – проверить состояние процесса и в случае его завершения заархивировать каталог и послать уведомление:

//import

//check state
Process p = getProcess ( … )
int state = p. getCompleteState ( … )
if ( state == 1 ) <
//doSomeLogicForArchive
Zip z = new Zip ( … )
z. makeZip ( … )
> else <
//doAnotherLogic
return
>
//doSomeLogicForSendNotify
Smtp smtp = new Smtp ( … )
Message m = new Message ( … )
smtp. send ( to,m. )

Код получается достаточно большим, и он будет понятен, в основном, лишь программистам. Давайте его упростим и вынесем три указанных действия в класс ArchiveScript со статическими методами. Скрипт после вынесения методов:

import ArchiveScript
if ( ArchiveScript. checkState ( ) ) <
ArchiveScript. makeArchive ( .. )
> else <
//doAnotherLogic
return
>
ArchiveScript. sendNotify ( … )

Уже лучше? — Лучше, но все еще есть артефакты – импорт и названия класса, которые тоже стоило бы убрать. И в Groovy есть подобная возможность – возможность задания базового класса для скрипта вне самого скрипта. Класс ArchiveScript для этого должен наследоваться от Script и методы могут быть не статическими. Код скрипта при этом еще упрощается – исчезает импорт и префикс класса:

if ( checkState ( ) ) <
makeArchive ( .. )
> else <
//doAnotherLogic
Return
>
sendNotify ( … )

Уже достаточно хорошо. В случае если код внутри блока ветвления условия однострочный, можно отказаться и от фигурных скобок. А в случае Groovy часто и от скобок справа от имени метода. Код выполнения скрипта немного усложняется — нужно создать объект CompilerConfiguration, установить значение scriptBaseClass равное имени созданного нами класса ArchiveScript и передать этот объект в GroovyShell:

CompilerConfiguration conf = new CompilerConfiguration ( ) ;
conf. setScriptBaseClass ( «package.ArchiveScript» ) ;
GroovyShell shell = new GroovyShell ( conf ) ;

Далее, давайте рассмотрим, как задаются параметры методов в скрипте при вызове. В случае определения в классе ArchiveScript метода makeArchive в таком виде:

def makeArchive ( sourcePath, destPath, deleteSource )

В скрипте вызов должен был бы выглядеть так:

makeArchive «x:/aaa/» , «x:/a.zip» , true

И если говорить о наглядности и даже удобстве Groovy позволяет нам сделать передачу параметров через именованные параметры, так:

makeArchive sourcePath: ‘x:/aaa/’ ,
destPath: ‘x:/a.zip’ ,
deleteSource: true

Однако в этом случае параметры будут передаваться внутри HashMap и, соответственно, получение параметров в makeArchive в классе ArchiveScript должно быть таким:

def makeArchive ( params ) <
makeArchiveInternal params. sourcePath , params. destPath , params. deleteSource
>

Если применить преобразование и для других вызовов, то в конечном итоге наш скрипт мог бы выглядеть так:

if ( checkState ( ‘SomeData’ ) ) <
makeArchive sourcePath: ‘c:/1/*.*’ ,
destPath: ‘c:/testarch.zip’ ,
deleteSource: true
> else <
//doAnotherLogic
Return
>
sendNotify to: ‘aaa@gdsl.ru’ , content: ‘сообщение’

И это уже не слишком сложный и достаточно читаемый код.

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

Замечу, что я рассмотрел лишь малую толику разработки DSL. В Groovy есть более широкая поддержка разработки своего DSL, а так же DSL-поддержка для Eclipse и для IntelliJ Idea

Тестирование

Хотелось бы сказать несколько слов о тестировании сценариев. Каким бы простым не был скрипт, ошибки могут быть и в нем. Даже если Вы пишите сценарии в IDE, полноценной проверки на корректность синтаксиса Вы можете не получить. Это возможно лишь при его выполнении. Так же необходимо проверять поведение скрипта.
Так как нам не хотелось бы выполнять реальные действия при выполнении тестирования сценария, то нужно каким-то образом заменить реальную логику на имитацию. Groovy позволяет нам это сделать многими путями. Покажу несколько из них.

Замена базового скрипта

Создаем новый класс ArchiveSciptMock который имеет интерфейс аналогичный ArchiveScript, и реализующий нужное нам поведение (или ничего не делающий). При создании объекта конфигурации CompilerConfiguration передаем его имя вместо оригинала.

CompilerConfiguration conf = new CompilerConfiguration ( ) ;
conf. setScriptBaseClass ( «package.ArchiveScriptMock» ) ;

Замена методов в базовом классе скрипта

Другим вариантом без создания дополнительного mock класса может быть замена методов на mock в самом ArchiveScript. В groovy это можно сделать, например, таким способом:

ArchiveScript. metaClass . with <
checkState < t ->true >
makeArchive < params ->>
sendNotify < params ->>
>
runScript ( )

Недостатком и первого и второго способа я бы считал необходимость написания дублирующей логики по проверки правильности передаваемых параметров. Так как если в ArchiveScriptMock метод makeArchive будет таким:

def makeArchive ( params ) <
//makeArchiveInternal params.sourcePath, params.destPath, params.deleteSource
>

То мы не проверим все ли параметры были переданы. Нужно будет писать что-то похожее на это:

def makeArchive ( params ) <
makeArchiveInternalMock params. sourcePath , params. destPath , params. deleteSource
>

Я бы предложил сделать небольшой рефакторинг ArchiveScript — сделать ArchiveScript фасадом и всю логику перенести в другой класс. Например, в Java класс Archive.
Рефакторинг — не только для целей тестирования, но и из других соображений, например, для отделения поведения от способа выполнения (нет зависимости от Script). В итоге, после изменения, ArchiveScript примет такой вид:

abstract class ArchiveScript extends Script <
Archive arc = new Archive ( )
def makeArchive ( params ) <
arc. archive params. sourcePath ,params. destPath , params. deleteSource
>

Теперь, можно тестировать логику и сценарий в отдельности. Заменим Archive на его mock и выполним скрипт:

//заменяем и выполняем скрипт так
Archive. metaClass . with ( mockedMethods )
runScript ( )

//или так
StubFor stub = new StubFor ( Archive ) ;
stub. demand . with <
mockedMethods
>
stub. use <
runScript ( )
>

Естественно поведение Archive можно заменить и с помощью java mock фреймворков, однако, нам пока достаточно и этого.

Итог

Я считаю, что получил достаточно гибкий инструмент для написания сценариев, без недостатков, озвученных в начале текста, а также достаточно простотой в использовании. Контроль корректности сценария так же не был потерян — использование mock поведения позволяет нам тестировать их в достаточной мере, перед реальным выполнением.

Проект с исходными кодами проекта — groovydsl. Компилируется Gradle через враппер.

Некоторые идеи взяты из книги Groovy for Domain-Specific Languiages, Fergal Dearle, 2010

Интеграция Groovy в приложения Java

В этом руководстве мы рассмотрим новейшие методы интеграции Groovy в приложение Java.

2. Несколько слов о Groovy​

Язык программирования Groovy — это мощный динамический язык с опциональной типизацией . Он поддерживается Apache Software Foundation и сообществом Groovy при участии более 200 разработчиков.

Его можно использовать для создания всего приложения, для создания модуля или дополнительной библиотеки, взаимодействующей с нашим кодом Java, или для запуска оцениваемых и компилируемых скриптов на лету.

3. Зависимости Maven​

На момент написания последнего стабильного выпуска была версия 2.5.7, а Groovy 2.6 и 3.0 (оба выпущены осенью 2017 года) все еще находятся в стадии альфа-тестирования.

Как и в Spring Boot, нам просто нужно включить groovy-all pom, чтобы добавить все зависимости , которые могут нам понадобиться, не беспокоясь об их версиях:

 dependency>   groupId>org.codehaus.groovygroupId>   artifactId>groovy-allartifactId>   version>$ version>   type>pomtype>   dependency> 

4. Совместная компиляция​

Прежде чем вдаваться в подробности настройки Maven, нам нужно понять, с чем мы имеем дело.

Наш код будет содержать файлы Java и Groovy . У Groovy не возникнет проблем с поиском классов Java, но что, если мы хотим, чтобы Java нашла классы и методы Groovy?

На помощь приходит совместная компиляция!

Совместная компиляция — это процесс, предназначенный для компиляции файлов Java и Groovy в одном проекте с помощью одной команды Maven.

При совместной компиляции компилятор Groovy:

  • разобрать исходные файлы
  • в зависимости от реализации создавать заглушки, совместимые с компилятором Java
  • вызвать компилятор Java для компиляции заглушек вместе с исходными кодами Java — таким образом классы Java могут найти зависимости Groovy
  • скомпилируйте исходники Groovy — теперь наши исходники Groovy могут найти свои Java-зависимости

В зависимости от реализующего его плагина нам может потребоваться разделить файлы на определенные папки или сообщить компилятору, где их найти.

Без совместной компиляции исходные файлы Java будут скомпилированы, как если бы они были исходными кодами Groovy. Иногда это может работать, поскольку большая часть синтаксиса Java 1.7 совместима с Groovy, но семантика будет другой.

5. Плагины компилятора Maven​

Доступно несколько подключаемых модулей компилятора, поддерживающих совместную компиляцию , каждый из которых имеет свои сильные и слабые стороны.

Двумя наиболее часто используемыми с Maven являются Groovy-Eclipse Maven и GMaven+.

5.1. Плагин Groovy-Eclipse Maven​

Плагин Groovy-Eclipse Maven упрощает совместную компиляцию, избегая генерации заглушек , что по-прежнему является обязательным шагом для других компиляторов, таких как GMaven + , но представляет некоторые особенности конфигурации.

Чтобы включить поиск новейших артефактов компилятора, мы должны добавить репозиторий Maven Bintray:

 pluginRepositories>   pluginRepository>   id>bintrayid>   name>Groovy Bintrayname>   url>https://dl.bintray.com/groovy/mavenurl>   releases>     updatePolicy>neverupdatePolicy>   releases>   snapshots>   enabled>falseenabled>   snapshots>   pluginRepository>   pluginRepositories> 

Затем в разделе плагинов мы сообщаем компилятору Maven, какую версию компилятора Groovy он должен использовать.

На самом деле плагин, который мы будем использовать — плагин компилятора Maven — на самом деле не компилирует, а вместо этого делегирует задание артефакту groovy-eclipse- batch :

 plugin>   artifactId>maven-compiler-pluginartifactId>   version>3.8.0version>   configuration>   compilerId>groovy-eclipse-compilercompilerId>   source>$ source>   target>$ target>   configuration>   dependencies>   dependency>   groupId>org.codehaus.groovygroupId>   artifactId>groovy-eclipse-compilerartifactId>   version>3.3.0-01version>   dependency>   dependency>   groupId>org.codehaus.groovygroupId>   artifactId>groovy-eclipse-batchartifactId>   version>$-01version>   dependency>   dependencies>   plugin> 

Версия зависимости groovy-all должна совпадать с версией компилятора.

Наконец, нам нужно настроить автообнаружение нашего исходного кода: по умолчанию компилятор будет искать в таких папках, как src/main/java и src/main/groovy, но если наша папка java пуста, компилятор не будет искать наш groovy. источники .

Тот же механизм действует и для наших тестов.

Чтобы принудительно обнаружить файл, мы могли бы добавить любой файл в src/main/java и src/test/java или просто добавить плагин groovy-eclipse-compiler :

 plugin>   groupId>org.codehaus.groovygroupId>   artifactId>groovy-eclipse-compilerartifactId>   version>3.3.0-01version>   extensions>trueextensions>   plugin> 

Раздел является обязательным, чтобы подключаемый модуль мог добавить дополнительную фазу сборки и цели, содержащие две исходные папки Groovy.

5.2. Плагин GMavenPlus​

Плагин GMavenPlus может иметь название, похожее на название старого плагина GMaven, но вместо создания простого патча автор приложил усилия, чтобы упростить компилятор и отделить его от конкретной версии Groovy .

Для этого плагин отделяется от стандартных рекомендаций для плагинов компилятора.

Компилятор GMavenPlus добавляет поддержку функций, которых в то время еще не было в других компиляторах , например, invokedynamic , консоль интерактивной оболочки и Android.

С другой стороны, это представляет некоторые сложности:

  • он изменяет исходные каталоги Maven, чтобы они содержали как исходники Java, так и исходники Groovy, но не заглушки Java.
  • это требует от нас управления заглушками , если мы не удаляем их с правильными целями

Для настройки нашего проекта нам нужно добавить gmavenplus-plugin :

 plugin>   groupId>org.codehaus.gmavenplusgroupId>   artifactId>gmavenplus-pluginartifactId>   version>1.7.0version>   executions>   execution>   goals>   goal>executegoal>   goal>addSourcesgoal>   goal>addTestSourcesgoal>   goal>generateStubsgoal>   goal>compilegoal>   goal>generateTestStubsgoal>   goal>compileTestsgoal>   goal>removeStubsgoal>   goal>removeTestStubsgoal>   goals>   execution>   executions>   dependencies>   dependency>   groupId>org.codehaus.groovygroupId>   artifactId>groovy-allartifactId>   = 1.5.0 should work here -->   version>2.5.6version>   scope>runtimescope>   type>pomtype>   dependency>   dependencies>   plugin> 

Чтобы можно было протестировать этот подключаемый модуль, мы создали в образце второй файл pom с именем gmavenplus-pom.xml .

5.3. Компиляция с помощью плагина Eclipse-Maven​

Теперь, когда все настроено, мы можем, наконец, построить наши классы.

В приведенном нами примере мы создали простое приложение Java в исходной папке src/main/java и несколько сценариев Groovy в папке src/ main/groovy , где мы можем создавать классы и сценарии Groovy.

Давайте соберем все с помощью плагина Eclipse-Maven:

 $ mvn clean compile  ...  [INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ core-groovy-2 ---  [INFO] Changes detected - recompiling the module!   [INFO] Using Groovy-Eclipse compiler to compile both Java and Groovy files  ... 

Здесь мы видим, что Groovy все компилирует .

5.4. Компиляция с помощью GMavenPlus​

GMavenPlus показывает некоторые отличия:

 $ mvn -f gmavenplus-pom.xml clean compile  ...  [INFO] --- gmavenplus-plugin:1.7.0:generateStubs (default) @ core-groovy-2 ---  [INFO] Using Groovy 2.5.7 to perform generateStubs.  [INFO] Generated 2 stubs.  [INFO]   ...  [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ core-groovy-2 ---  [INFO] Changes detected - recompiling the module!   [INFO] Compiling 3 source files to XXX\ForEach\TutorialsRepo\core-groovy-2\target\classes  [INFO]   ...  [INFO] --- gmavenplus-plugin:1.7.0:compile (default) @ core-groovy-2 ---  [INFO] Using Groovy 2.5.7 to perform compile.  [INFO] Compiled 2 files.  [INFO]   ...  [INFO] --- gmavenplus-plugin:1.7.0:removeStubs (default) @ core-groovy-2 ---  [INFO]   ... 

Сразу замечаем, что GMavenPlus проходит дополнительные этапы:

  1. Генерация заглушек, по одной для каждого groovy-файла
  2. Компиляция файлов Java — как заглушек, так и кода Java
  3. Компиляция файлов Groovy

Генерируя заглушки, GMavenPlus наследует слабость, доставлявшую много головной боли разработчикам в последние годы при работе с совместной компиляцией.

В идеальном сценарии все будет работать просто отлично, но если добавить больше шагов, у нас будет больше точек отказа: например, сборка может завершиться ошибкой до того, как удастся очистить заглушки.

Если это произойдет, оставшиеся старые заглушки могут запутать нашу IDE, что затем покажет ошибки компиляции, хотя мы знаем, что все должно быть правильно.

Только чистая сборка позволит избежать мучительной и долгой охоты на ведьм.

5.5. Упаковка зависимостей в файле JAR​

Чтобы запустить программу как jar из командной строки , мы добавили maven-assembly-plugin , « который будет включать все зависимости Groovy в «толстую банку» с именем с постфиксом, определенным в свойстве descriptorRef:

 plugin>   groupId>org.apache.maven.pluginsgroupId>   artifactId>maven-assembly-pluginartifactId>   version>3.1.0version>   configuration>     descriptorRefs>   descriptorRef>jar-with-dependenciesdescriptorRef>   descriptorRefs>     archive>   manifest>   mainClass>com.foreach.MyJointCompilationAppmainClass>   manifest>   archive>   configuration>   executions>   execution>   id>make-assemblyid>     phase>packagephase>   goals>   goal>singlegoal>   goals>   execution>   executions>   plugin> 

После завершения компиляции мы можем запустить наш код с помощью этой команды:

 $ java -jar target/core-groovy-2-1.0-SNAPSHOT-jar-with-dependencies.jar com.foreach.MyJointCompilationApp 

6. Загрузка кода Groovy на лету​

Компиляция Maven позволяет нам включать файлы Groovy в наш проект и ссылаться на их классы и методы из Java.

Хотя этого недостаточно, если мы хотим изменить логику во время выполнения: компиляция выполняется за пределами этапа выполнения, поэтому нам все равно нужно перезапустить наше приложение, чтобы увидеть наши изменения .

Чтобы воспользоваться динамической мощью (и рисками) Groovy, нам нужно изучить методы, доступные для загрузки наших файлов, когда наше приложение уже запущено.

6.1. GroovyClassLoader ​

Для этого нам нужен GroovyClassLoader, который может анализировать исходный код в текстовом или файловом формате и генерировать результирующие объекты класса.

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

Вместо этого скрипт, поступающий непосредственно из объекта String , не будет кэшироваться , поэтому многократный вызов одного и того же скрипта все равно может привести к утечке памяти.

GroovyClassLoader — это основа, на которой строятся другие системы интеграции.

Реализация относительно проста:

 private final GroovyClassLoader loader;    private Double addWithGroovyClassLoader(int x, int y)   throws IllegalAccessException, InstantiationException, IOException    Class calcClass = loader.parseClass(   new File("src/main/groovy/com/foreach/", "CalcMath.groovy"));   GroovyObject calc = (GroovyObject) calcClass.newInstance();   return (Double) calc.invokeMethod("calcSum", new Object[]  x, y >);   >    public MyJointCompilationApp()    loader = new GroovyClassLoader(this.getClass().getClassLoader());   // .   > 

6.2. GroovyShell ​

Метод parse() загрузчика сценариев оболочки принимает исходные тексты в текстовом или файловом формате и создает экземпляр класса Script .

Этот экземпляр наследует метод run() от Script , который выполняет весь файл сверху донизу и возвращает результат, заданный последней выполненной строкой.

Если мы хотим, мы также можем расширить Script в нашем коде и переопределить реализацию по умолчанию, чтобы напрямую вызывать нашу внутреннюю логику.

Реализация вызова Script.run() выглядит следующим образом:

 private Double addWithGroovyShellRun(int x, int y) throws IOException    Script script = shell.parse(new File("src/main/groovy/com/foreach/", "CalcScript.groovy"));   return (Double) script.run();   >    public MyJointCompilationApp()    // .   shell = new GroovyShell(loader, new Binding());   // .   > 

Обратите внимание, что run() не принимает параметры, поэтому нам нужно будет добавить в наш файл некоторые глобальные переменные, чтобы инициализировать их через объект Binding .

Поскольку этот объект передается при инициализации GroovyShell , переменные совместно используются всеми экземплярами сценария .

Если мы предпочитаем более детальное управление, мы можем использовать invokeMethod() , который может обращаться к нашим собственным методам через отражение и напрямую передавать аргументы.

Давайте посмотрим на эту реализацию:

 private final GroovyShell shell;    private Double addWithGroovyShell(int x, int y) throws IOException    Script script = shell.parse(new File("src/main/groovy/com/foreach/", "CalcScript.groovy"));   return (Double) script.invokeMethod("calcSum", new Object[]  x, y >);   >    public MyJointCompilationApp()    // .   shell = new GroovyShell(loader, new Binding());   // .   > 

Под прикрытием GroovyShell полагается на GroovyClassLoader для компиляции и кэширования результирующих классов, поэтому те же правила, которые объяснялись ранее, применяются таким же образом.

6.3. GroovyScriptEngine ​

Класс GroovyScriptEngine особенно подходит для тех приложений, которые полагаются на повторную загрузку скрипта и его зависимостей .

Хотя у нас есть эти дополнительные функции, реализация имеет лишь несколько небольших отличий:

 private final GroovyScriptEngine engine;    private void addWithGroovyScriptEngine(int x, int y) throws IllegalAccessException,   InstantiationException, ResourceException, ScriptException    ClassGroovyObject> calcClass = engine.loadScriptByName("CalcMath.groovy");   GroovyObject calc = calcClass.newInstance();   Object result = calc.invokeMethod("calcSum", new Object[]  x, y >);   LOG.info("Result of CalcMath.calcSum() method is <>", result);   >    public MyJointCompilationApp()    ...   URL url = null;   try    url = new File("src/main/groovy/com/foreach/").toURI().toURL();   > catch (MalformedURLException e)    LOG.error("Exception while creating url", e);   >   engine = new GroovyScriptEngine(new URL[] url>, this.getClass().getClassLoader());   engineFromFactory = new GroovyScriptEngineFactory().getScriptEngine();   > 

На этот раз нам нужно настроить исходные корни, и мы ссылаемся на скрипт только по его имени, что немного чище.

Заглянув внутрь метода loadScriptByName , мы сразу видим проверку isSourceNewer, где движок проверяет, действителен ли источник, находящийся в данный момент в кеше.

Каждый раз, когда наш файл изменяется, GroovyScriptEngine автоматически перезагружает этот конкретный файл и все классы, зависящие от него.

Хотя это удобная и мощная функция, она может вызвать очень опасный побочный эффект: многократная перезагрузка огромного количества файлов приведет к перегрузке процессора без предупреждения.

Если это произойдет, нам может потребоваться реализовать собственный механизм кэширования для решения этой проблемы.

6.4. GroovyScriptEngineFactory (JSR-223)​

JSR-223 предоставляет стандартный API для вызова фреймворков сценариев, начиная с Java 6.

Реализация выглядит аналогично, хотя мы возвращаемся к загрузке через полные пути к файлам:

 private final ScriptEngine engineFromFactory;    private void addWithEngineFactory(int x, int y) throws IllegalAccessException,   InstantiationException, javax.script.ScriptException, FileNotFoundException    Class calcClas = (Class) engineFromFactory.eval(   new FileReader(new File("src/main/groovy/com/foreach/", "CalcMath.groovy")));   GroovyObject calc = (GroovyObject) calcClas.newInstance();   Object result = calc.invokeMethod("calcSum", new Object[]  x, y >);   LOG.info("Result of CalcMath.calcSum() method is <>", result);   >    public MyJointCompilationApp()    // .   engineFromFactory = new GroovyScriptEngineFactory().getScriptEngine();   > 

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

7. Подводные камни динамической компиляции​

Используя любой из вышеперечисленных методов, мы могли бы создать приложение, которое считывает скрипты или классы из определенной папки вне нашего jar-файла .

Это дало бы нам возможность добавлять новые функции во время работы системы (если только нам не требуется новый код в части Java), тем самым достигая своего рода разработки с непрерывной доставкой.

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

8. Подводные камни при запуске Groovy в Java-проекте​

8.1. Производительность​

Все мы знаем, что когда системе требуется высокая производительность, необходимо следовать некоторым золотым правилам.

Два из них могут иметь большее значение для нашего проекта:

  • избегать отражения
  • минимизировать количество инструкций байт-кода

Отражение, в частности, является дорогостоящей операцией из-за процесса проверки класса, полей, методов, параметров метода и так далее.

Если мы проанализируем вызовы методов из Java в Groovy, например, при запуске примера addWithCompiledClasses , стек операций между .calcSum и первой строкой фактического метода Groovy выглядит так:

 calcSum:4, CalcScript (com.foreach)  addWithCompiledClasses:43, MyJointCompilationApp (com.foreach)  addWithStaticCompiledClasses:95, MyJointCompilationApp (com.foreach)  main:117, App (com.foreach) 

Что соответствует Java. То же самое происходит, когда мы приводим объект, возвращенный загрузчиком, и вызываем его метод.

Однако вот что делает вызов invokeMethod :

 calcSum:4, CalcScript (com.foreach)  invoke0:-1, NativeMethodAccessorImpl (sun.reflect)  invoke:62, NativeMethodAccessorImpl (sun.reflect)  invoke:43, DelegatingMethodAccessorImpl (sun.reflect)  invoke:498, Method (java.lang.reflect)  invoke:101, CachedMethod (org.codehaus.groovy.reflection)  doMethodInvoke:323, MetaMethod (groovy.lang)  invokeMethod:1217, MetaClassImpl (groovy.lang)  invokeMethod:1041, MetaClassImpl (groovy.lang)  invokeMethod:821, MetaClassImpl (groovy.lang)  invokeMethod:44, GroovyObjectSupport (groovy.lang)  invokeMethod:77, Script (groovy.lang)  addWithGroovyShell:52, MyJointCompilationApp (com.foreach)  addWithDynamicCompiledClasses:99, MyJointCompilationApp (com.foreach)  main:118, MyJointCompilationApp (com.foreach) 

В этом случае мы можем оценить, что на самом деле стоит за мощью Groovy: метакласс .

Метакласс определяет поведение любого заданного класса Groovy или Java, поэтому Groovy просматривает его всякий раз, когда требуется выполнить динамическую операцию , чтобы найти целевой метод или поле. После обнаружения стандартный поток отражения выполняет его.

Два золотых правила нарушены одним методом вызова!

Если нам нужно работать с сотнями динамических файлов Groovy, то, как мы вызываем наши методы, будет иметь огромное значение в производительности нашей системы.

8.2. Метод или свойство не найдено​

Как упоминалось ранее, если мы хотим развернуть новые версии файлов Groovy в жизненном цикле компакт-диска, нам нужно обращаться с ними так, как будто они являются API, отдельным от нашей базовой системы.

Это означает введение нескольких отказоустойчивых проверок и ограничений на дизайн кода, чтобы наш недавно присоединившийся разработчик не взорвал производственную систему неправильным толчком.

Примеры каждого из них: наличие конвейера CI и использование устаревшего метода вместо удаления.

Что произойдет, если мы этого не сделаем? Мы получаем ужасные исключения из-за отсутствующих методов и неправильного количества и типов аргументов.

И если мы думаем, что компиляция нас спасет, давайте посмотрим на метод calcSum2() наших скриптов Groovy:

 // this method will fail in runtime  def calcSum2(x, y)    // DANGER! The variable "log" may be undefined   log.info "Executing $x + $y"   // DANGER! This method doesn't exist!   calcSum3()   // DANGER! The logged variable "z" is undefined!   log.info("Logging an undefined variable: $z")   > 

Просматривая весь файл, мы сразу видим две проблемы: метод calcSum3() и переменная z нигде не определены.

Тем не менее, скрипт компилируется успешно, без единого предупреждения, как статически в Maven, так и динамически в GroovyClassLoader.

Это не удастся, только когда мы попытаемся вызвать его.

Статическая компиляция Maven покажет ошибку только в том случае, если наш Java-код ссылается непосредственно на calcSum3() после приведения GroovyObject , как мы делаем в методе addWithCompiledClasses() , но это все еще неэффективно, если вместо этого мы используем отражение.

9. Заключение​

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

Как обычно, исходный код, использованный в примерах, можно найти на GitHub .

  • 1. Введение
  • 2. Несколько слов о Groovy
  • 3. Зависимости Maven
  • 4. Совместная компиляция
  • 5. Плагины компилятора Maven
    • 5.1. Плагин Groovy-Eclipse Maven
    • 5.2. Плагин GMavenPlus
    • 5.3. Компиляция с помощью плагина Eclipse-Maven
    • 5.4. Компиляция с помощью GMavenPlus
    • 5.5. Упаковка зависимостей в файле JAR
    • 6.1. GroovyClassLoader
    • 6.2. GroovyShell
    • 6.3. GroovyScriptEngine
    • 6.4. GroovyScriptEngineFactory (JSR-223)
    • 8.1. Производительность
    • 8.2. Метод или свойство не найдено

    Как из groovy скрипта вызвать java метод

    The Groovy language proposes several ways to integrate itself into applications (Java or even Groovy) at runtime, from the most basic, simple code execution to the most complete, integrating caching and compiler customization.

    All the examples written in this section are using Groovy, but the same integration mechanisms can be used from Java.
    1.1. Eval

    The groovy.util.Eval class is the simplest way to execute Groovy dynamically at runtime. This can be done by calling the me method:

    Eval supports multiple variants that accept parameters for simple evaluation:

    1 Simple evaluation with one bound parameter named x
    2 Same evaluation, with a custom bound parameter named k
    3 Simple evaluation with two bound parameters named x and y
    4 Simple evaluation with three bound parameters named x , y and z

    The Eval class makes it very easy to evaluate simple scripts, but doesn’t scale: there is no caching of the script, and it isn’t meant to evaluate more than one-liners.

    1.2. GroovyShell
    1.2.1. Multiple sources

    The groovy.lang.GroovyShell class is the preferred way to evaluate scripts with the ability to cache the resulting script instance. Although the Eval class returns the result of the execution of the compiled script, the GroovyShell class offers more options.

    1 create a new GroovyShell instance
    2 can be used as Eval with direct execution of the code
    3 can read from multiple sources ( String , Reader , File , InputStream )
    4 can defer execution of the script. parse returns a Script instance
    5 Script defines a run method
    1.2.2. Sharing data between a script and the application

    It is possible to share data between the application and the script using a groovy.lang.Binding :

    1 create a new Binding that will contain shared data
    2 create a GroovyShell using this shared data
    3 add a string to the binding
    4 add a date to the binding (you are not limited to simple types)
    5 evaluate the script

    Note that it is also possible to write from the script into the binding:

    1 create a new Binding instance
    2 create a new GroovyShell using that shared data
    3 use an undeclared variable to store the result into the binding
    4 read the result from the caller

    It is important to understand that you need to use an undeclared variable if you want to write into the binding. Using def or an explicit type like in the example below would fail because you would then create a local variable:

    You must be very careful when using shared data in a multithreaded environment. The Binding instance that you pass to GroovyShell is not thread safe, and shared by all scripts.

    It is possible to work around the shared instance of Binding by leveraging the Script instance which is returned by parse :

    1 will store the x variable inside b1
    2 will store the x variable inside b2

    However, you must be aware that you are still sharing the same instance of a script. So this technique cannot be used if you have two threads working on the same script. In that case, you must make sure of creating two distinct script instances:

    1 create an instance of script for thread 1
    2 create an instance of script for thread 2
    3 assign first binding to script 1
    4 assign second binding to script 2
    5 start first script in a separate thread
    6 start second script in a separate thread
    7 wait for completion

    In case you need thread safety like here, it is more advisable to use the GroovyClassLoader directly instead.

    1.2.3. Custom script class

    We have seen that the parse method returns an instance of groovy.lang.Script , but it is possible to use a custom class, given that it extends Script itself. It can be used to provide additional behavior to the script like in the example below:

    The custom class defines a property called name and a new method called greet . This class can be used as the script base class by using a custom configuration:

    1 create a CompilerConfiguration instance
    2 instruct it to use MyScript as the base class for scripts
    3 then use the compiler configuration when you create the shell
    4 the script now has access to the new method greet
    You are not limited to the sole scriptBaseClass configuration. You can use any of the compiler configuration tweaks, including the compilation customizers.
    1.3. GroovyClassLoader

    In the previous section, we have shown that GroovyShell was an easy tool to execute scripts, but it makes it complicated to compile anything but scripts. Internally, it makes use of the groovy.lang.GroovyClassLoader , which is at the heart of the compilation and loading of classes at runtime.

    By leveraging the GroovyClassLoader instead of GroovyShell , you will be able to load classes, instead of instances of scripts:

    1 create a new GroovyClassLoader
    2 parseClass will return an instance of Class
    3 you can check that the class which is returns is really the one defined in the script
    4 and you can create a new instance of the class, which is not a script
    5 then call any method on it
    A GroovyClassLoader keeps a reference of all the classes it created, so it is easy to create a memory leak. In particular, if you execute the same script twice, if it is a String, then you obtain two distinct classes!
    1 dynamically create a class named «Foo»
    2 create an identical looking class, using a separate parseClass call
    3 make sure both classes have the same name
    4 but they are actually different!

    The reason is that a GroovyClassLoader doesn’t keep track of the source text. If you want to have the same instance, then the source must be a file, like in this example:

    1 parse a class from a File
    2 parse a class from a distinct file instance, but pointing to the same physical file
    3 make sure our classes have the same name
    4 but now, they are the same instance

    Using a File as input, the GroovyClassLoader is capable of caching the generated class file, which avoids creating multiple classes at runtime for the same source.

    1.4. GroovyScriptEngine

    The groovy.util.GroovyScriptEngine class provides a flexible foundation for applications which rely on script reloading and script dependencies. While GroovyShell focuses on standalone Script`s and `GroovyClassLoader handles dynamic compilation and loading of any Groovy class, the GroovyScriptEngine will add a layer on top of GroovyClassLoader to handle both script dependencies and reloading.

    To illustrate this, we will create a script engine and execute code in an infinite loop. First of all, you need to create a directory with the following script inside:

    then you can execute this code using a GroovyScriptEngine :

    1 create a script engine which will look for sources into our source directory
    2 execute the script, which will return an instance of Greeter
    3 print the greeting message

    At this point, you should see a message printed every second:

    Without interrupting the script execution, now replace the contents of the ReloadingTest file with:

    And the message should change to:

    But it is also possible to have a dependency on another script. To illustrate this, create the following file into the same directory, without interrupting the executing script:

    and update the ReloadingTest script like this:

    And this time, the message should change to:

    And as a last test, you can update the Dependency.groovy file without touching the ReloadingTest file:

    And you should observe that the dependent file was reloaded:

    1.5. CompilationUnit

    Ultimately, it is possible to perform more operations during compilation by relying directly on the org.codehaus.groovy.control.CompilationUnit class. This class is responsible for determining the various steps of compilation and would let you introduce new steps or even stop compilation at various phases. This is for example how stub generation is done, for the joint compiler.

    However, overriding CompilationUnit is not recommended and should only be done if no other standard solution works.

    2. JSR 223 javax.script API

    JSR-223 is a standard API for calling scripting frameworks in Java. It is available since Java 6 and aims at providing a common framework for calling multiple languages from Java. Groovy provides its own richer integration mechanisms, and if you don’t plan to use multiple languages in the same application, it is recommended that you use the Groovy integration mechanisms instead of the limited JSR-223 API.

    Here is how you need to initialize the JSR-223 engine to talk to Groovy from Java:

    Calling a Groovy function from Java

    How do you call a function defined in a Groovy script file from Java?

    Example groovy script:

    I’ve looked at the GroovyShell, GroovyClassLoader, and GroovyScriptEngine.

    6 Answers 6

    Assuming you have a file called test.groovy , which contains (as in your example):

    Then you can create a file Runner.java like this:

    compile it with:

    (Note: The warnings are left as an exercise to the reader) ��

    Then, you can run this Runner.class with:

    The simplest way is to compile the script into a java class file and just call it directly. Example:

    1. Compile as ataylor suggests
    2. Use JSR-223 as explained here
    3. If you are using Spring, have a groovy class that implements a Java interface, and inject into your code with:

    One advantage of the spring approach is the concept of ‘refreshable beans’. That is, Spring can be configured to monitor your script file for modifications, and replace at runtime.

    You too can use the Bean Scripting Framework to embed any scripting language into your Java code. BSF give you the opportunity of integrate other languages, but is not native integration.

    If you are clearly focused to use Groovy the GroovyScriptEngine is the most complete solution.

    Использование возможностей Groovy DSL для конфигурации Java-приложения

    Всем привет! Я хотел бы рассказать историю о страшных конфигах и как их удалось причесать и сделать вменяемыми. Я работаю над довольно большим и относительно старым проектом, который постоянно допиливается и разрастается. Конфигурация задается с помощью маппинга xml-файлов на java-бины. Не самое лучшее решение, но оно имеет свои плюсы — например, при создании сервиса можно передать ему бин с конфигурацией, отвечающий за его раздел. Однако, есть и минусы. Самый существенный из них — нет нормального наследования профилей конфигурации. В какой-то момент я осознал, что для того, чтобы поменять одну настройку, я должен отредактировать около 30 xml-файлов, по одному для каждого из профилей. Так больше продолжаться не могло, и было принято волевое решение все переписать.

    Требования

    • Наследование и переопределение (или fallback). Должна быть возможность задать некий базовый профиль, унаследовать от него дочерние и переопределить или добавить в них те места, которые необходимо
    • Маппинг в java-бины. Переписывать по всему проекту использование конфигурации с бинов на проперти вида mongodb.directory.host не хотелось, использовать map-ы из map-ов тоже.
    • Возможность писать в конфиге комментарии. Не критично, но удобно и приятно.

    Хотелось бы, чтобы конфиг выглядел примерно так:

    Как я этого добился — под катом.

    Может, для этого уже есть библиотека?

    Скорее всего, да. Однако, из тех, что я нашел и посмотрел, мне ничего не подошло. Большинство из них рассчитаны на чтение конфигов, объединение их в один большой и затем работу с полученным конфигом через отдельные проперти. Маппинг на бины почти никто не умеет, а писать несколько десятков адаптеров-конвертеров слишком долго. Самой перспективной показалась lightbend config, с ее симпатичным форматом HOCON и наследованием/переопределением из коробки. И она даже почти смогла заполнить java-бин, но, как оказалось, она не умеет map-ы и очень плохо расширяется. Пока я с ней экспериментировал, на получившиеся конфиги посмотрел коллега и сказал: «Чем-то это напоминает Groovy DSL». Так было принято решение использовать именно его.

    Что это такое?

    DSL (domain-specific language, предметно-ориентированный язык) — язык, «заточенный» под определенную область применения, в нашем случае — под конфигурацию конкретно нашего приложения. Пример можно посмотреть в спойлере перед катом.

    Запускать groovy-скрипты из java-приложения легко. Нужно всего лишь добавить groovy в зависимости, например, Gradle

    и использовать GroovyShell

    Как это работает?

    Вся магия основывается на двух вещах.

    Делегирование

    Для начала, скрипт на groovy компилируется в байткод, для него создается свой класс, а при запуске скрипта вызывается метод run() этого класса, содержащий весь код скрипта. Если скрипт возвращает какое-то значение, то мы можем получить его как результат выполнения evaluate() . В принципе, можно было бы в скрипте создавать наши бины с конфигурацией и возвращать их, но в таком случае мы не получим красивого синтаксиса.

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

    @Data — аннотация из библиотеки lombok: добавляет геттеры и сеттеры к полям и реализует toString, equals и hashCode. Благодаря ей POJO превращается в бин.

    GroovyObjectSupport — базовый класс для «java-объектов, которые хотят казаться groovy-объектами» (как написано в документации). Позже я покажу, для чего именно он нужен. На данном этапе можно обойтись без него, но пусть будет сразу.

    Теперь создадим скрипт, который будет заполнять его поля.

    Тут все очевидно. Пока, как вы видите, мы не используем каких-то фич DSL, о них я расскажу позже.

    И, наконец, запустим его из джавы

    ServerConfig(name=MyTestServer, description=Apache Tomcat) — результат lombok-овской реализации toString().

    Как видите, все довольно просто. Конфиг — настоящий исполняемый groovy-код, в нем можно использовать все фичи языка, например, подстановки

    вернет нам ServerConfig(name=MyTest server, description=Apache Tomcat server)

    И в этом скрипте даже можно ставить брейкпоинты и дебажить!

    Вызов методов

    Теперь перейдем к собственно DSL. Допустим, мы хотим добавить в наш конфиг настройки коннекторов. Выглядят они примерно так:

    Добавим поля для двух коннекторов, http и https, в наш конфиг сервера:

    Мы можем задать коннекторы из скрипта с помощью вот такого groovy-кода

    ServerConfig(name=MyTest, description=Apache Tomcat, http=Connector(port=80, secure=false), https=null)

    Как видите, это сработало, но, конечно же, для конфигурации такой синтаксис совершенно не подходит. Перепишем конфиг так, как хотелось бы, чтобы он выглядел:

    Exception in thread «main» groovy.lang.MissingMethodException: No signature of method: config.http() is applicable for argument types: (config$_run_closure1) values: [config$_run_closure1@780cb77] .

    Похоже, мы пытаемся вызвать метод http(Closure) , и groovy не может найти его ни у нашего объекта-делегата, ни у скрипта. Мы могли бы, конечно, объявить его в классе ServersConfig:

    И аналогичный — для https. На этот раз все хорошо:

    ServerConfig(name=MyTest, description=Apache Tomcat, http=Connector(port=80, secure=false), https=Connector(port=443, secure=true))

    Здесь надо пояснить, что же мы сделали, потому что это первый шаг к DSL. Мы объявили метод, который принимает параметром groovy.lang.Closure , создает новый объект для поля нашего конфига, делегирует его полученному замыканию и выполняет код замыкания. Строка

    означает, что при обращении к полям или методам groovy будет сначала смотреть на делегат, и только потом, если не найдет ничего подходящего — на замыкание. Для скрипта эта стратегия используется по умолчанию, для замыкания ее надо устанавливать вручную.

    Библиотека logback, имеющая возможность конфигурации через groovy, использует именно такой подход. Они явным образом реализовали все методы, которые используются в их DSL.

    В принципе, у нас уже есть некий DSL, но он далек от идеального. Во-первых, хотелось бы избежать ручного написания кода для установки каждого поля, а во-вторых, хотелось бы избежать дублирования кода для всех классов бинов, которые используются у нас в конфиге. И здесь нам на помощь приходит второй компонент магии groovy DSL.

    methodMissing()

    Каждый раз, когда groovy встречает вызов метода, отсутствующего у объекта, он пытается вызвать methodMissing(). В качестве параметров туда передается имя метода, который попытались вызвать, и список его аргументов. Уберем из класса ServerConfig методы http и https и объявим вместо них следующее:

    args на самом деле имеет тип Object[] , но groovy ищет метод именно с такой сигнатурой. Проверим:

    То, что нужно! Осталось только развернуть аргументы и в зависимости от типа параметра устанавливать значения полей. В нашем случае туда передается массив из одного элемента класса Closure. Сделаем, например, вот так:

    Я опускаю почти все проверки и ловлю исключений, чтобы не захламлять код. В реальном проекте, естественно, прямо так делать нельзя.

    Здесь мы видим сразу несколько вызовов, специфичных для groovy-объектов.

    • смотрим, что вызванный метод совпадает по имени с одним из полей с помощью обращения к метаклассу. Метакласс присутствует у каждого groovy-объекта и работает примерно как reflection, но удобнее. Метакласс, в частности, позволяет получать информацию о полях и доступ к ним через аксессоры, даже если сами поля приватные. Это нам еще пригодится позже.
    • получаем тип поля через тот же метакласс, чтобы создать новый экземпляр его. Здесь мы рассчитываем на то, что у всех классов, которые мы собираемся использовать в конфигах, задан конструктор по умолчанию, но в принципе никто не мешает сделать тут настолько сложную логику, насколько вам необходимо.
    • получаем значение поля через getProperty() и устанавливаем новое значение через setProperty(). Это методы из GroovyObjectSupport и они обращаются к полю через аксессоры, если найдет их, или напрямую. Это избавляет нас от необходимости изменять поле через reflection или еще какими-то не очень удобными способами, особенно, если это поле где-то в классе-наследнике.

    Пока что мы добавили methodMissing и все dsl-плюшки только для одного класса, ServerConfig. Мы могли бы реализовать тот же метод для Connection, но зачем дублировать код? Создадим какой-нибудь базовый для всех наших конфиг-бинов класс, скажем, GroovyConfigurable, перенесем methodMissing в него, а ServerConfig и Connector унаследуем.

    Это все работает, даже при том, что GroovyConfigurable ничего не знает о полях своих наследников!

    Наследование

    Следующий шаг — сделать возможность включать в конфиг некий родительский конфиг и переопределять какие-то отдельные поля. Выглядеть это должно примерно так.

    Groovy позволяет импортировать классы, но не скрипты. Самый простой способ — реализовать в нашем классе GroovyConfigurable метод include. Добавим туда путь к самому скрипту и пару методов:

    Сделаем конфиг parent.groovy, в котором опишем некий базовый конфиг:

    В config.groovy оставим только то, что мы хотим переопределить:

    ServerConfig(name=MyTest, description=PARENT DESCRIPTION, http=Connector(port=80, secure=false), https=Connector(port=8080, secure=true))

    Как видите, name переопределилось, как и поле port в https. Поле secure в нем осталось от родительского конфига.

    Можно пойти еще дальше и сделать возможность инклюдить не весь конфиг, а его отдельные части! Для этого в methodMissing надо добавить проверку на то, что устанавливаемое поле тоже GroovyConfigurable и задать ему путь к родительскому скрипту.

    Это позволит нам инклюдить не только весь скрипт, но и его части! Например, так

    Русские Блоги

    Во многих случаях Groovy является идеальным инструментом для выполнения определенных задач, таких как быстрое создание прототипов или создание модульных приложений, которые могут быть расширены с помощью макросов или плагинов. Эти расширения могут быть реализованы в Groovy и могут быть встроены в ваше приложение без утомительного цикла разработки и развертывания. Я считаю, что Groovy чрезвычайно выразителен, а его простота и мощные функции принесут большую пользу для разработки вашей программы.

    В других случаях Groovy может быть не лучшим выбором. Это особенно подходит для приложений с высокими требованиями к производительности, потому что компромисс между гибкостью и скоростью работы неизбежен.

    Самым большим преимуществом Groovy является идеальная интеграция с Java. Гибкость и маневренность Groovy позволяют разработчикам интегрироваться с Java как минимум пятью способами. Конечно, у каждого способа есть свои преимущества и недостатки. В следующих главах будут подробно рассмотрены эти 5 методов интеграции и даны применимые условия каждого метода.

    Компилировать в байт-код (Compilingto Bytecode)

    Самый простой и самый прямой метод интеграции — это программирование файлов Groovy в байт-код Java (т. Е. Файлов классов) и возможность получения этих файлов по пути классов Java (classpath). Этот подход также имеет недостатки: с одной стороны, вы должны сначала скомпилировать все файлы Groovy, и в то же время, некоторые другие классы Java, на которые есть ссылки в некоторых файлах Groovy, также должны сначала скомпилироваться, а затем возникнут проблемы с компиляцией.

    Использовать GroovyShell (UsingGroovyShell)

    GroovyShell позволяет вычислять произвольные выражения Groovy в классах Java (даже в классах Groovy). Вы можете использовать объект Binding для ввода параметров в выражение и, наконец, вернуть результат вычисления выражения Groovy через GroovyShell. В листинге 2.19 показано, как использовать GroovyShell.

    Листинг 2.19. Приложение GroovyShell

    GroovyShell — эффективный инструмент для вывода выражений динамического типа. Типичным приложением является вывод значения динамического выражения Groovy, введенного пользователем. Обычно существует пользовательский интерфейс (UI), например приложение для работы с электронными таблицами.

    Использовать GroovyScriptEngine (UsingGroovyScriptEngine)

    GroovyShell в основном используется для вывода противоположных сценариев или выражений. Если он заменен несколькими сценариями, связанными друг с другом, лучше использовать GroovyScriptEngine. GroovyScriptEngine загружает скрипты Groovy из указанного вами местоположения (файловая система, URL, база данных и т. Д.) И перезагружает их при изменении скрипта. Как и GroovyShell, GroovyScriptEngine также позволяет передавать значения параметров и может возвращать значение сценария.

    Предположим, у вас есть простой скрипт Groovy, C: \ tmp \ SimpleScript.groovy, как показано ниже:

    //SimpleScript.groovy

    В листинге 2.20 показано, как передать параметры в GroovyScriptEngine и выполнить скрипт SimpleScript.groovy, а затем вернуть значение.

    Перечисление 2.20 GroovyScriptEngineExample.java

    Хотя GroovyScriptEngine является эффективным методом для выполнения нескольких скриптов Groovy, он по-прежнему не может правильно обрабатывать сложные классы. В случае одновременной обработки классов и сценариев Groovy наиболее полным решением является GroovyClassLoader (фактически, GroovyShell и GroovyScriptEngine будут использовать его)

    Использовать GroovyClassLoader (UsingGroovyClassLoader)

    GroovyClassLoader — это пользовательский загрузчик классов, который отвечает за интерпретацию и загрузку классов Groovy, используемых в классах Java. Это также может быть скомпилировано. В листинге 2.21 показано, как использовать GroovyClassLoader для загрузки класса Groovy и вызова метода этого класса.

    Листинг 2.21. Приложение GroovyClassLoader

    Один из типичных сценариев использования GroovyClassLoader — это наличие интерфейса Java и класса Groovy, который реализует интерфейс Java. Поэтому вы можете использовать GroovyClassLoader для загрузки класса реализации Groovy в приложение, чтобы вы могли напрямую вызывать методы интерфейса, как показано в листинге 2.22.

    Листинг 2.22. Реализация интерфейса Java в Groovy

    Используйте JSR223 (Используя JSR 223)

    Если вы используете Java 6, вы можете использовать запрос спецификации Java (JSR) Sun 223: Сценарии для платформы Java. Использование JSR 223 может отделить ваше приложение от конкретного механизма сценариев, что позволяет легко изменять язык сценариев. Если вы хотите использовать другие языки сценариев (например, BeanShell или JRuby) в своем коде Java, рекомендуется JSR 223. Если вы не используете Java 6 и хотите выбрать смешанное программирование на нескольких языках, вы можете обратиться к платформе Apache Bean Scripting:http://jakarta.apache.org/bsf, Если вы не хотите отделить свое приложение от определенного механизма сценариев, использование собственного метода Groovy является относительно гибким.

    В листинге 2.23 показано, как использовать JSR223 для интеграции Groovy. Вы должны убедиться, что файл groovy-engine.jar уже находится в вашем пути к классам. Ты можешь пойти вhttps://scripting.dev.java.net скачать это jar файл. В этом примере показано, как вызвать метод Groovy, передать параметры и вернуть значение.

    Листинг 2.23. Использование JSR 223

    2.4 Краткое содержание этой главы

    В этой главе я кратко представил некоторые важные различия между Java и Groovy. Не беспокойтесь, если вы не полностью поняли все содержание этой главы, потому что остальная часть этой книги снова обсудит их подробно. Цель этой главы — дать вам понять, «как Java — это Groovy, а Groovy — это не Java», и заставить вас поверить, что Groovy предоставляет множество ценных вещей для программистов на Java.

    В то же время в этой главе также показано, как интегрировать код Groovy в код Java. При интеграции с Java вы найдете Groovy чрезвычайно гибким и мобильным. В конце концов, Groovy — это не альтернатива Java, а эффективное дополнение к Java.

    Далее в этой книге будет подробно обсуждаться Groovy, а также приведены более практичные и масштабируемые примеры, которые помогут читателям понять и гибко применять Groovy. В следующей главе вы обсудите типы данных Groovy, коллекции и управляющие структуры.

    Похожие публикации:

    1. Could not create the java virtual machine что делать
    2. Internal exception java net socketexception connection reset как исправить
    3. Java tm 6 update что это за программа
    4. Javascript node js что это

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *