Groovy integrálása a Java alkalmazásokba

1. Bemutatkozás

Ebben az oktatóanyagban a Groovy Java alkalmazásba történő integrálásának legújabb technikáit tárjuk fel.

2. Néhány szó Groovyról

A Groovy programozási nyelv hatékony, opcionálisan gépelt és dinamikus nyelv. Az Apache Software Foundation és a Groovy közösség támogatja, több mint 200 fejlesztő közreműködésével.

Használható egy teljes alkalmazás felépítésére, modul vagy kiegészítő könyvtár létrehozására, amely kölcsönhatásba lép a Java kóddal, vagy menet közben kiértékelt és összeállított szkripteket futtathat.

További információért olvassa el a Bevezetés a Groovy nyelvbe című részt, vagy keresse fel a hivatalos dokumentációt.

3. Maven-függőségek

Az írás idején a legújabb stabil kiadás 2,5,7, míg a Groovy 2.6 és a 3.0 (mindkettő 17 őszén kezdődött) még mindig alfa stádiumban van.

Hasonló a tavaszi bakancshoz, csak be kell foglalnunk a nagyszerű pom az összes függőség hozzáadásához szükségünk lehet, anélkül, hogy aggódnánk a verzióik miatt:

 org.codehaus.groovy groovy-all $ {groovy.version} pom 

4. Közös összeállítás

Mielőtt belemennénk a Maven konfigurálásának részleteibe, meg kell értenünk, hogy miről van szó.

Kódunk Java és Groovy fájlokat egyaránt tartalmaz. Groovynak egyáltalán nem lesz semmi problémája a Java osztályok megtalálásával, de mi van akkor, ha azt akarjuk, hogy a Java megtalálja a Groovy osztályokat és módszereket?

Itt jön a közös összeállítás a megmentésre!

A közös fordítás olyan folyamat, amelyet a Java és a Groovy összeállítására terveztek fájlok ugyanabban a projektben, egyetlen Maven paranccsal.

Közös fordítással a Groovy fordító:

  • a forrásfájlok elemzése
  • a megvalósítástól függően hozzon létre olyan csonkokat, amelyek kompatibilisek a Java fordítóval
  • hívja meg a Java fordítót, hogy fordítsa le a csonkokat a Java forrásokkal együtt - így a Java osztályok megtalálhatják a Groovy függőségeket
  • állítsa össze a Groovy forrásokat - most a Groovy forrásaink megtalálják Java függőségeiket

A telepítő plugintól függően szükség lehet a fájlok külön mappákra szétválasztására, vagy meg kell mondanunk a fordítónak, hogy hol találja meg őket.

Közös fordítás nélkül a Java forrásfájlokat úgy állítanák össze, mintha Groovy források lennének. Néha ez működhet, mivel a Java 1.7 szintaxis nagy része kompatibilis a Groovy-val, de a szemantika más lenne.

5. Maven Compiler beépülő modulok

Van néhány fordítói bővítmény, amely támogatja a közös fordítást, mindegyik annak erősségeivel és gyengeségeivel.

A két leggyakrabban használt Maven a Groovy-Eclipse Maven és a GMaven +.

5.1. A Groovy-Eclipse Maven beépülő modul

A Groovy-Eclipse Maven plugin egyszerűsíti a közös összeállítást azáltal, hogy elkerüli a csonkok keletkezését, továbbra is kötelező lépés más fordítók, például a GMaven számára+, de bemutat néhány konfigurációs furcsaságot.

A legújabb fordítótermékek visszakeresésének engedélyezéséhez hozzá kell adnunk a Maven Bintray adattárat:

  bintray Groovy Bintray //dl.bintray.com/groovy/maven soha nem hamis 

Ezután a plugin szakaszban elmondjuk a Maven fordítónak, hogy melyik Groovy fordító verziót kell használnia.

Valójában az általunk használt plugin - a Maven fordító plugin - valójában nem fordítja le, hanem átruházza a feladatot a groovy-eclipse-batch műalkotás:

 maven-compiler-plugin 3.8.0 groovy-eclipse-compiler $ {java.version} $ {java.version} org.codehaus.groovy groovy-eclipse-compiler 3.3.0-01 org.codehaus.groovy groovy-eclipse-batch $ {groovy.version} -01 

A nagyszerű függőségi verziónak meg kell egyeznie a fordító verziójával.

Végül konfigurálnunk kell a forrás automatikus felfedezésünket: alapértelmezés szerint a fordító olyan mappákat keres, mint pl src / main / java és src / main / groovy, de ha a java mappánk üres, a fordító nem keresi a groovy forrásainkat.

Ugyanez a mechanizmus érvényes a tesztjeinkre is.

A fájlfelfedezés kényszerítésére bármilyen fájlt hozzáadhatunk a fájlhoz src / main / java és src / test / java, vagy egyszerűen adja hozzá a groovy-eclipse-fordító csatlakoztat:

 org.codehaus.groovy groovy-eclipse-compiler 3.3.0-01 igaz 

A szakasz kötelező, hogy a plugin hozzáadja az extra összeállítási fázist és célokat, amelyek tartalmazzák a két Groovy forrásmappát.

5.2. A GMavenPlus beépülő modul

A GMavenPlus beépülő modul neve hasonló lehet a régi GMaven beépülő modulhoz, de puszta javítás létrehozása helyett a szerző arra törekedett, hogy egyszerűsítse és válassza le a fordítót egy adott Groovy verzióról.

Ehhez a beépülő modul elkülönül a fordítói beépülő modulok szabványos irányelveitől.

A GMavenPlus fordító támogatja azokat a funkciókat, amelyek akkor még nem voltak jelen más fordítókban, például az invokedynamic, az interaktív shell konzol és az Android.

A másik oldalon néhány szövődményt mutat:

  • azt módosítja Maven forráskönyvtárait hogy tartalmazzák mind a Java, mind a Groovy forrásokat, a Java csonkokat azonban nem
  • azt megköveteli tőlünk a csonkok kezelését ha nem töröljük őket a megfelelő célokkal

A projektünk konfigurálásához hozzá kell adnunk a gmavenplus-plugint:

 org.codehaus.gmavenplus gmavenplus-plugin 1.7.0 futtatja az addSources addTestSources generál Stubs fordítást generáljaTestStubs fordításTesztek eltávolítja a Stubs eltávolítástTesztStubs org.codehaus.groovy groovy-all = 1.5.0 itt kell működnie -> 2.5.6 futásidejű pom 

A plugin tesztelésének engedélyezéséhez létrehoztunk egy második pom fájlt gmavenplus-pom.xml a mintában.

5.3. Fordítás az Eclipse-Maven beépülő modullal

Most, hogy minden konfigurálva van, végre felépíthetjük az osztályainkat.

A megadott példában létrehoztunk egy egyszerű Java alkalmazást a forrás mappában src / main / java és néhány Groovy-szkript be src / main / groovy, ahol létrehozhatunk Groovy osztályokat és szkripteket.

Építsünk fel mindent az Eclipse-Maven pluginnal:

$ mvn clean compile ... [INFO] --- maven-compiler-plugin: 3.8.0: fordítás (alapértelmezett fordítás) @ core-groovy-2 --- [INFO] Változások észlelve - a modul újrafordítása! [INFO] A Groovy-Eclipse fordító használata Java és Groovy fájlok fordításához ...

Itt azt látjuk Groovy mindent összeállít.

5.4. Összeállítás a GMavenPlus-szal

A GMavenPlus mutat néhány különbséget:

$ mvn -f gmavenplus-pom.xml tiszta fordítás ... [INFO] --- gmavenplus-plugin: 1.7.0: generatorStubs (alapértelmezett) @ core-groovy-2 --- [INFO] A Groovy 2.5.7 használata végezze el a GenerStubs-t [INFO] 2 csonkot generált. [INFO] ... [INFO] --- maven-compiler-plugin: 3.8.1: fordítás (alapértelmezett fordítás) @ core-groovy-2 --- [INFO] Változások észlelve - a modul újrafordítása! [INFO] 3 forrásfájl összeállítása a XXX \ Baeldung \ TutorialsRepo \ core-groovy-2 \ target \ osztályokba [INFO] ... [INFO] --- gmavenplus-plugin: 1.7.0: fordítás (alapértelmezett) @ core- groovy-2 --- [INFO] A Groovy 2.5.7 használata a fordításhoz. [INFO] 2 fájlt állított össze. [INFO] ... [INFO] --- gmavenplus-plugin: 1.7.0: removeStubs (alapértelmezett) @ core-groovy-2 --- [INFO] ...

Rögtön észrevesszük, hogy a GMavenPlus a következő lépéseken megy keresztül:

  1. Csonkok létrehozása, minden egyes groovy fájlhoz egy
  2. A Java fájlok fordítása - a csonkok és a Java kód egyaránt
  3. A Groovy fájlok fordítása

Csonkok generálásával a GMavenPlus örököl egy gyengeséget, amely az elmúlt években sok fejfájást okozott a fejlesztőknek, amikor közös fordítással dolgoztak.

Az ideális forgatókönyv szerint minden rendben működne, de ha több lépést vezetünk be, akkor több kudarcpontunk is van: például az összeállítás meghiúsulhat, mielőtt meg tudja tisztítani a csonkokat.

Ha ez megtörténik, a körülöttünk hagyott régi csonkok megzavarhatják az IDE-t, ami aztán fordítási hibákat mutat, ahol tudjuk, hogy minden helyesnek kell lennie.

Csak a tiszta felépítéssel lehetne elkerülni a fájdalmas és hosszú boszorkányüldözést.

5.5. Csomagolási függőségek a Jar fájlban

Nak nek futtassa a programot jarként a parancssorból, hozzáadtuk a maven-assembly-plugin, amely tartalmazza az összes Groovy-függőséget egy „kövér edényben”, amelyet a tulajdonságban definiált postfix-el neveznek el descriptorRef:

 org.apache.maven.plugins maven-assembly-plugin 3.1.0 jar-with-dependencies com.baeldung.MyJointCompilationApp make-assembly csomag egyetlen 

A fordítás befejezése után futtathatjuk a kódunkat ezzel a paranccsal:

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

6. A Groovy-kód betöltése menet közben

A Maven-összeállítás lehetővé tette, hogy a projektünkbe vonjuk be a Groovy fájlokat, és az osztályaikat és módszereiket a Java-ról lehívjuk.

Bár ez nem elég, ha futás közben szeretnénk megváltoztatni a logikát: az összeállítás a futásidőn kívül fut, tehát még mindig újra kell indítanunk az alkalmazást, hogy lássuk a változásainkat.

A Groovy dinamikus erejének (és kockázatainak) kihasználásához meg kell vizsgálnunk a fájljaink betöltésére rendelkezésre álló technikákat, amikor az alkalmazásunk már fut.

6.1. GroovyClassLoader

Ennek eléréséhez szükségünk van a GroovyClassLoader, amely szöveg- vagy fájlformátumban elemezheti a forráskódot és előállíthatja a kapott osztályobjektumokat.

Ha a forrás fájl, akkor a fordítási eredmény is tárolásra kerül, hogy elkerülje az általános költségeket, amikor a betöltőnek ugyanazon osztály több példányát kérjük.

A szkript közvetlenül a Húr objektum helyett nem kerül gyorsítótárba, ezért ugyanazon szkript többszöri meghívása továbbra is memóriaszivárgást okozhat.

GroovyClassLoader ez az alap, amelyre a többi integrációs rendszer épül.

A megvalósítás viszonylag egyszerű:

magán végső GroovyClassLoader rakodó; private Double addWithGroovyClassLoader (int x, int y) az IllegalAccessException, InstantiationException, IOException dobja {Class calcClass = loader.parseClass (új File ("src / main / groovy / com / baeldung /", "CalcMath.groovy")); GroovyObject calc = (GroovyObject) calcClass.newInstance (); return (Double) calc.invokeMethod ("calcSum", új objektum [] {x, y}); } public MyJointCompilationApp () {loader = new GroovyClassLoader (this.getClass (). getClassLoader ()); // ...} 

6.2. GroovyShell

A Shell Script Loader elemzés () metódus elfogadja a szöveges vagy fájlformátumú forrásokat és generál egy példányát a Forgatókönyv osztály.

Ez a példány örökli a fuss() módszer től Forgatókönyv, amely a teljes fájlt felülről lefelé hajtja végre, és az utolsó végrehajtott sor eredményét adja vissza.

Ha akarjuk, kiterjeszthetjük is Forgatókönyv kódunkban, és felülírja az alapértelmezett megvalósítást, hogy közvetlenül meghívjuk a belső logikánkat.

A végrehajtás hívni Script.run () így néz ki:

private Double addWithGroovyShellRun (int x, int y) dobja az IOException {Script script = shell.parse (új File ("src / main / groovy / com / baeldung /", "CalcScript.groovy")); return (Double) script.run (); } public MyJointCompilationApp () {// ... shell = new GroovyShell (loader, new Binding ()); // ...} 

Felhívjuk figyelmét, hogy a fuss() nem fogadja el a paramétereket, ezért hozzá kell adnunk a fájlunkhoz néhány globális változót a Kötés tárgy.

Amint ezt az objektumot átadják a GroovyShell inicializáláskor a változókat megosztják az összes Forgatókönyv példányok.

Ha jobban szemléltetjük a kontrollt, használhatjuk invokeMethod (), amely reflexió útján hozzáférhet saját módszereinkhez, és közvetlenül adhat érveket.

Nézzük meg ezt a megvalósítást:

privát végső GroovyShell héj; private Double addWithGroovyShell (int x, int y) dobja az IOException {Script script = shell.parse (új File ("src / main / groovy / com / baeldung /", "CalcScript.groovy")); return (Double) script.invokeMethod ("calcSum", új objektum [] {x, y}); } public MyJointCompilationApp () {// ... shell = new GroovyShell (loader, new Binding ()); // ...} 

Takaró alatt, GroovyShell támaszkodik a GroovyClassLoader az eredményül kapott osztályok összeállításához és gyorsítótárazásához, tehát ugyanazok a korábban ismertetett szabályok érvényesek.

6.3. GroovyScriptEngine

A GroovyScriptEngine osztály különösen azokra az alkalmazásokra vonatkozik, amelyek támaszkodhat egy szkript újratöltésére és annak függőségeire.

Bár rendelkezünk ezekkel a további funkciókkal, a megvalósításnak csak néhány apró különbsége van:

privát végső GroovyScriptEngine motor; private void addWithGroovyScriptEngine (int x, int y) az IllegalAccessException, InstantiationException, ResourceException, ScriptException {Class calcClass = motor.loadScriptByName ("CalcMath.groovy") dobja; GroovyObject calc = calcClass.newInstance (); Objektum eredménye = calc.invokeMethod ("calcSum", új objektum [] {x, y}); LOG.info ("A CalcMath.calcSum () metódus eredménye {}", eredmény); } public MyJointCompilationApp () {... URL url = null; próbáld meg az {url = új Fájl ("src / main / groovy / com / baeldung /"). toURI (). toURL (); } catch (MalformedURLException e) {LOG.error ("Kivétel az URL létrehozása közben", e); } motor = new GroovyScriptEngine (új URL [] {url}, this.getClass (). getClassLoader ()); engineFromFactory = új GroovyScriptEngineFactory (). getScriptEngine (); }

Ezúttal konfigurálnunk kell a forrásgyökereket, és csak a nevével hivatkozunk a szkriptre, ami kicsit tisztább.

Belenéz a loadScriptByName módszerrel azonnal láthatjuk a csekket isSourceNewer ahol a motor ellenőrzi, hogy a jelenleg gyorsítótárban lévő forrás továbbra is érvényes-e.

Minden alkalommal, amikor a fájlunk megváltozik, GroovyScriptEngine automatikusan újratölti az adott fájlt és az attól függően az összes osztályt.

Bár ez egy praktikus és hatékony szolgáltatás, nagyon veszélyes mellékhatást okozhat: sokszor nagyszámú fájl újratöltése figyelmeztetés nélkül a CPU általános költségeit eredményezi.

Ha ez megtörténik, előfordulhat, hogy a probléma kezeléséhez saját gyorsítótár-mechanizmust kell bevezetnünk.

6.4. GroovyScriptEngineFactory (JSR-223)

A JSR-223 a szabványos API a szkript keretek meghívására a Java 6 óta.

A megvalósítás hasonlónak tűnik, bár a teljes fájl elérési útvonalon keresztül térünk vissza:

privát végleges ScriptEngine engineFromFactory; private void addWithEngineFactory (int x, int y) az IllegalAccessException, InstantiationException, javax.script.ScriptException, FileNotFoundException {Class calcClas = (Class) engineFromFactory.eval (új FileReader (új fájl ("src / main / gro / groovy)" elemeket dobja) "," CalcMath.groovy "))); GroovyObject calc = (GroovyObject) calcClas.newInstance (); Objektum eredménye = calc.invokeMethod ("calcSum", új objektum [] {x, y}); LOG.info ("A CalcMath.calcSum () metódus eredménye {}", eredmény); } public MyJointCompilationApp () {// ... engineFromFactory = new GroovyScriptEngineFactory (). getScriptEngine (); }

Nagyon jó, ha több szkriptnyelvvel integráljuk az alkalmazásunkat, de funkciókészlete korlátozottabb. Például, nem támogatja az osztály újratöltését. Mint ilyen, ha csak Groovyval integrálódunk, akkor jobb lehet ragaszkodni a korábbi megközelítésekhez.

7. A dinamikus fordítás buktatói

A fenti módszerek bármelyikével létrehozhatunk egy alkalmazást, amely szkripteket vagy osztályokat olvas a jar fájlunkon kívüli adott mappából.

Ez megadná nekünk a rugalmasság új funkciók hozzáadásához a rendszer futása közben (hacsak nem igényelünk új kódot a Java részben), ezáltal valamiféle folyamatos továbbfejlesztést érünk el.

De vigyázzon erre a kétélű kardra: most nagyon óvatosan kell megvédenünk magunkat hibák, amelyek fordítási és futási időben egyaránt előfordulhatnak, de facto biztosítja, hogy a kódunk biztonságosan meghibásodjon.

8. A Groovy futtatásának buktatói egy Java projektben

8.1. Teljesítmény

Mindannyian tudjuk, hogy amikor egy rendszernek nagyon teljesítőnek kell lennie, néhány aranyszabályt be kell tartania.

Kettő nagyobb súlyt jelenthet a projektünkben:

  • kerülje a reflexiót
  • minimalizálja a bytecode utasítások számát

A tükrözés különösen költséges művelet az osztály, a mezők, a módszerek, a metódus paraméterek stb. Ellenőrzésének folyamata miatt.

Ha a példa futtatásakor elemezzük a metódushívásokat például Java-ról Groovy-ra addWithCompiledClasses, a művelet halmaza között .calcSum és a tényleges Groovy-módszer első sora így néz ki:

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

Ami összhangban van a Java-val. Ugyanez történik akkor is, amikor leadjuk a betöltő által visszaküldött objektumot, és meghívjuk annak metódusát.

Azonban ez az, amit invokeMethod hívás:

calcSum: 4, CalcScript (com.baeldung) 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, MetaClassMet ((MetaClassImpl) , MetaClassImpl (groovy.lang) invokeMethod: 44, GroovyObjectSupport (groovy.lang) invokeMethod: 77, Script (groovy.lang) addWithGroovyShell: 52, MyJointCompilationApp (com.baeldung) addWithDynamicCompiled , MyJointCompilationApp (com.baeldung)

Ebben az esetben értékelni tudjuk, mi rejlik valójában Groovy ereje mögött: a MetaClass.

A MetaClass meghatározza az adott Groovy vagy Java osztály viselkedését, ezért Groovy megvizsgálja, amikor dinamikus művelet van végrehajtva a megcélzott módszer vagy mező megtalálása érdekében. Miután megtalálta, a szokásos reflexiós áramlat hajtja végre.

Két aranyszabály megsértve egy invoke módszerrel!

Ha dinamikus Groovy fájlok százával kell dolgoznunk, az, hogy hogyan hívjuk a módszereinket, akkor hatalmas teljesítmény-különbséget fog hozni rendszerünkben.

8.2. Módszer vagy tulajdonság nem található

Mint korábban említettük, ha akarjuk telepítse a Groovy fájlok új verzióit egy CD életciklusában meg kell kezelje őket úgy, mintha API-k lennének külön a mi alaprendszerünktől.

Ez azt jelenti, hogy a helyére kerül több hibamentes ellenőrzés és a kód kialakításának korlátozása így újonnan csatlakozott fejlesztőnk nem robbantja fel rosszul a termelési rendszert.

Példák a következőkre: CI-csővezeték megléte és törlés helyett metódus elavulás alkalmazása.

Mi történik, ha nem tesszük? Rettenetes kivételeket kapunk a hiányzó módszerek és a hibás argumentumszám és típusok miatt.

És ha úgy gondoljuk, hogy az összeállítás megmentene minket, nézzük meg a módszert calcSum2 () a Groovy-szkriptjeink közül:

// ez a metódus sikertelen lesz a fut futás alatt def calcSum2 (x, y) {// VESZÉLY! A "log" változó undefined lehet log.info "$ x + $ y végrehajtása" // VESZÉLY! Ez a módszer nem létezik! calcSum3 () // VESZÉLY! A "z" naplózott változó nincs meghatározva! log.info ("Egy meghatározatlan változó naplózása: $ z")}

A teljes fájl átnézésével azonnal két problémát látunk: a módszert calcSum3 () és a változó z nincsenek meghatározva sehol.

Ennek ellenére a szkriptet sikeresen lefordítják, egyetlen figyelmeztetés nélkül is, mind statikusan Mavenben, mind dinamikusan a GroovyClassLoader-ben.

Csak akkor fog kudarcot vallani, ha megpróbáljuk hivatkozni rá.

A Maven statikus összeállítása csak akkor mutat hibát, ha a Java kódunk közvetlenül erre utal calcSum3 (), miután leadta a GroovyObject mint mi a addWithCompiledClasses () módszer, de akkor is hatástalan, ha inkább reflexiót használunk.

9. Következtetés

Ebben a cikkben azt kutattuk, hogyan integrálhatjuk a Groovyt a Java alkalmazásunkba, megvizsgálva a különböző integrációs módszereket és néhány problémát, amelyekkel vegyes nyelvekkel találkozhatunk.

Szokás szerint a példákban használt forráskód megtalálható a GitHubon.


$config[zx-auto] not found$config[zx-overlay] not found