Java kommentárok feldolgozása és készítő létrehozása

1. Bemutatkozás

Ez a cikk az intro Java forrásszintű kommentárok feldolgozásához és példákat mutat be ennek a technikának a használatával további forrásfájlok létrehozásához a fordítás során.

2. A kommentárok feldolgozásának alkalmazásai

A forrásszintű kommentár-feldolgozás először a Java 5-ben jelent meg. Ez egy praktikus technika további forrásfájlok generálásához a fordítási szakaszban.

A forrásfájloknak nem feltétlenül Java fájloknak kell lenniük - a forráskód annotációi alapján bármilyen leírást, metaadatokat, dokumentációt, erőforrásokat vagy bármilyen más típusú fájlt generálhat.

A feljegyzések feldolgozását sok mindenütt elérhető Java könyvtárban használják, például metaclasserek előállítására a QueryDSL és a JPA-ban, az osztályok bővítéséhez a Lombok könyvtárban lévő kazánlap kóddal.

Fontos megjegyezni az annotáció-feldolgozó API korlátozása - csak új fájlok létrehozására használható, a meglévők megváltoztatására nem.

A figyelemre méltó kivétel a Lombok könyvtár, amely az annotáció feldolgozását bootstrapping mechanizmusként használja, hogy bekapcsolja magát a fordítási folyamatba és módosítsa az AST-t néhány belső fordító API-n keresztül. Ennek a hacky technikának semmi köze nincs az annotáció feldolgozásának rendeltetéséhez, ezért ebben a cikkben nem tárgyaljuk.

3. Annotation Processing API

Az annotáció feldolgozása több körben történik. Minden forduló azzal kezdődik, hogy a fordító megkeresi a forrásfájlokban található annotációkat, és kiválasztja az annotációkhoz alkalmas annotációs processzorokat. Mindegyik annotációs processzort viszont meghívják a megfelelő forrásokra.

Ha a folyamat során bármilyen fájl keletkezik, akkor egy másik kör indul a létrehozott fájlokkal a bemenetként. Ez a folyamat addig folytatódik, amíg a feldolgozási szakaszban nem jön létre új fájl.

Mindegyik annotációs processzort viszont meghívják a megfelelő forrásokra. Ha a folyamat során bármilyen fájl keletkezik, akkor egy másik forduló indul el, a bemenetként létrehozott fájlokkal. Ez a folyamat addig folytatódik, amíg a feldolgozási szakaszban nem jön létre új fájl.

Az annotációfeldolgozó API az javax.annotation.processing csomag. A fő felület, amelyet végre kell hajtania, a Processzor interfész, amelynek részleges megvalósítása van Absztrakt processzor osztály. Ez az osztály lesz az, amelyet kibővítünk saját annotációs processzorunk létrehozására.

4. A projekt beállítása

Az annotáció-feldolgozás lehetőségeinek bemutatásához kifejlesztünk egy egyszerű processzort folyékony objektum-építők előállításához annotált osztályokhoz.

A projektünket két Maven modulra bontjuk. Egyikük, annotation-processzor modul magában foglalja a processzort az annotációval együtt, és egy másik, a annotation-user modul tartalmazni fogja az annotált osztályt. Ez az annotáció-feldolgozás tipikus felhasználási esete.

A annotation-processzor modul a következő. A Google automatikus szolgáltatási könyvtárát fogjuk használni a processzor metaadatfájljának előállításához, amelyet később tárgyalunk, és a maven-compiler-plugin a Java 8 forráskódra hangolva. Ezen függőségek verzióit kibontják a tulajdonságok szakaszba.

Az automatikus szolgáltatási könyvtár és a maven-compiler-plugin legújabb verziói megtalálhatók a Maven Central adattárban:

 1.0-rc2 3.5.1 com.google.auto.service auto-service $ {auto-service.version} feltéve org.apache.maven.plugins maven-compiler-plugin $ {maven-compiler-plugin.version} 1,8 1,8 

A annotation-user A feljegyzett forrásokkal rendelkező Maven modul nem igényel különösebb hangolást, kivéve a függőségek szakaszban az annotáció-processzor modul függőségének hozzáadását:

 com.baeldung annotation-processing 1.0.0-SNAPSHOT 

5. Megjegyzés meghatározása

Tegyük fel, hogy van egy egyszerű POJO osztályunk annotation-user modul több mezővel:

public class Személy {private int age; privát karakterlánc neve; // szerelők és beállítók…}

Szeretnénk létrehozni egy építő segítő osztályt a Személy osztály folyékonyabban:

Személy személy = new PersonBuilder () .setAge (25) .setName ("John") .build ();

Ez PersonBuilder osztály nyilvánvaló választás egy generáció számára, mivel felépítését a Személy szetter módszerek.

Hozzunk létre egy @BuilderProperty annotáció a annotation-processzor modul a szetter módszerekhez. Ez lehetővé teszi számunkra a Építész osztály minden osztályhoz, amelynek szetter módszerei fel vannak jegyezve:

@Target (ElementType.METHOD) @Retention (RetentionPolicy.SOURCE) public @interface BuilderProperty {}

A @Cél annotáció a ElementType.METHOD A paraméter biztosítja, hogy ezt az annotációt csak egy metódusra lehessen tenni.

A FORRÁS megőrzési házirend azt jelenti, hogy ez a megjegyzés csak a forrás feldolgozása során érhető el, és futás közben nem érhető el.

A Személy osztály, amelynek tulajdonságai a @BuilderProperty a kommentár a következőképpen fog kinézni:

public class Személy {private int age; privát karakterlánc neve; @BuilderProperty public void setAge (int age) {this.age = age; } @BuilderProperty public void setName (karakterlánc neve) {this.name = név; } // getters…}

6. Megvalósítása a Processzor

6.1. Létrehozása Absztrakt processzor Alosztály

Kezdjük a Absztrakt processzor osztályon belül annotation-processzor Maven modul.

Először meg kell adnunk azokat a megjegyzéseket, amelyeket ez a processzor képes feldolgozni, és a támogatott forráskód verziót is. Ez megtehető a módszerek megvalósításával is getSupportedAnnotationTypes és getSupportedSourceVersion a Processzor kezelőfelületén vagy osztályozásával @SupportedAnnotationTypes és @SupportedSourceVersion annotációk.

A @AutoService az annotáció a autószerviz könyvtárat, és lehetővé teszi a processzor metaadatainak létrehozását, amelyet a következő szakaszokban ismertetünk.

@SupportedAnnotationTypes ("com.baeldung.annotation.processor.BuilderProperty") @SupportedSourceVersion (SourceVersion.RELEASE_8) @AutoService (Processor.class) nyilvános osztály BuilderProcessor meghosszabbítja az AbstractProcessor {@Overnide Set returnot ; }}

Megadhatja nemcsak a konkrét annotációs osztályneveket, hanem a helyettesítő karaktereket is „Com.baeldung.annotation. *” a jegyzetek feldolgozásához a com.baeldung.annotation csomag és annak összes alcsomagja, vagy akár “*” az összes feljegyzés feldolgozásához.

Az egyetlen módszer, amelyet végre kell hajtanunk, a folyamat módszer, amely maga végzi a feldolgozást. A fordító meghívja minden olyan forrásfájlra, amely tartalmazza a megfelelő jegyzeteket.

Elsőnek a kommentárokat adják át Jelölések beállítása argumentumot, és az aktuális feldolgozási körre vonatkozó információt a RoundEnviroment roundEnv érv.

A visszatérés logikai érték legyen igaz ha az annotációs processzora feldolgozta az összes továbbított jegyzetet, és nem szeretné, hogy azokat továbbítsák a listán szereplő többi jegyzetfeldolgozónak.

6.2. Adat gyűjtés

Processzorunk még nem igazán csinál semmi hasznosat, ezért töltsük ki kóddal.

Először meg kell ismételnünk az osztályban található összes felirat típusát - esetünkben a annotációk a halmaznak egyetlen eleme lesz, amely megfelel a @BuilderProperty megjegyzés, még akkor is, ha ez a megjegyzés többször előfordul a forrásfájlban.

Ennek ellenére jobb a folyamat módszer mint iterációs ciklus, a teljesség kedvéért:

@Orride public boolean process (Annotációk beállítása, RoundEnvironment roundEnv) {for (TypeElement annotation: annotations) {Set annotatedElements = roundEnv.getElementsAnnotatedWith (annotation); //…} return true; }

Ebben a kódban a Környezet példány, hogy megkapja az @BuilderProperty annotáció. A Személy osztályban ezek az elemek megfelelnek a setName és setAge mód.

@BuilderProperty Az annotation felhasználója tévesen jelölhet olyan módszereket, amelyek valójában nem beállítók. A szetter metódus nevének kezdőbetűvel kell kezdődnie készlet, és a metódusnak egyetlen argumentumot kell kapnia. Válasszuk hát el a búzát a pelyvától.

A következő kódban a Collectors.partitioningBy () gyűjtő az annotált módszerek két gyűjteményre bontására: helyesen feljegyzett beállítókra és más hibásan feljegyzett módszerekre:

Térkép annotatedMethods = annotatedElements.stream (). collect (Collectors.partitioningBy (element -> ((ExecutableType) element.asType ()). getParameterTypes (). size () == 1 && element.getSimpleName (). toString (). kezdődik ("készlet"))); Lista-beállítók = annotatedMethods.get (true); List otherMethods = annotatedMethods.get (false);

Itt használjuk a Element.asType () metódus a TypeMirror osztály, amely némi lehetőséget kínál a típusok önvizsgálatára, annak ellenére, hogy csak a forrásfeldolgozás szakaszában vagyunk.

Figyelmeztetnünk kell a felhasználót a helytelenül feljegyzett módszerekről, ezért használjuk a Messenger a AbstractProcessor.processingEnv védett mező. A következő sorok hibát jelenítenek meg minden hibásan jegyzetelt elemnél a forrás feldolgozási szakaszában:

otherMethods.forEach (elem -> processingEnv.getMessager (). printMessage (Diagnostic.Kind.ERROR, "A @BuilderProperty alkalmazást a setXxx" + "metódusra kell alkalmazni" egyetlen argumentummal, elem) ";

Természetesen, ha a helyes beállítók gyűjteménye üres, akkor nincs értelme folytatni az aktuális típusú elemkészlet iterációját:

if (setters.isEmpty ()) {folytatás; }

Ha a setters gyűjtemény legalább egy elemet tartalmaz, akkor azt fogjuk használni, hogy a mellékelt elemből megkapjuk a teljesen minősített osztálynevet, amely a setter módszer esetén maga a forrásosztály tűnik:

String className = ((TypeElement) setters.get (0) .getEnclosingElement ()). GetQualifiedName (). ToString ();

Az utolsó kis információ, amelyre szükségünk van egy készítő osztály létrehozásához, egy térkép a beállítók és argumentumtípusaik neve között:

Térkép setterMap = setters.stream (). Collect (Collectors.toMap (setter -> setter.getSimpleName (). ToString (), setter -> ((ExecutableType) setter.asType ()) .getParameterTypes (). Get (0) .toString ()));

6.3. A kimeneti fájl előállítása

Most már rendelkezésünkre áll minden információ, amelyre szükségünk van egy építőosztály létrehozásához: a forrásosztály neve, minden beállító neve és argumentumtípusa.

A kimeneti fájl előállításához a Filer az objektum által a AbstractProcessor.processingEnv védett ingatlan:

JavaFileObject builderFile = processingEnv.getFiler () .createSourceFile (builderClassName); próbáld ki (PrintWriter out = new PrintWriter (builderFile.openWriter ())) {// a létrehozott fájl kiírása ...}

A teljes kód a writeBuilderFile módszert az alábbiakban közöljük. Csak a forrás nevét és a készítő osztályt kell kiszámítanunk a csomag nevére, a teljes minősítésű készítő osztály nevére, valamint az egyszerű osztályok nevére. A kód többi része meglehetősen egyszerű.

private void writeBuilderFile (String className, Map setterMap) dobja az IOException {String packageName = null; int lastDot = className.lastIndexOf ('.'); if (lastDot> 0) {packName = className.substring (0, lastDot); } String simpleClassName = className.substring (lastDot + 1); String builderClassName = className + "Builder"; String builderSimpleClassName = builderClassName .substring (lastDot + 1); JavaFileObject builderFile = processingEnv.getFiler () .createSourceFile (builderClassName); próbáld ki (PrintWriter out = new PrintWriter (builderFile.openWriter ())) {if (packageName! = null) {out.print ("csomag"); out.print (csomagNév); out.println (";"); out.println (); } out.print ("nyilvános osztály"); out.print (builderSimpleClassName); out.println ("{"); out.println (); out.print ("privát"); out.print (simpleClassName); out.print ("objektum = új"); out.print (simpleClassName); out.println ("();"); out.println (); out.print ("nyilvános"); out.print (simpleClassName); out.println ("build () {"); out.println ("visszatérési objektum;"); out.println ("}"); out.println (); setterMap.entrySet (). forEach (setter -> {String methodName = setter.getKey (); String argumentType = setter.getValue (); out.print ("public"); out.print (builderSimpleClassName); out.print ( ""); out.print (methodName); out.print ("("); out.print (argumentumTípus); out.println ("érték" {"); out.print (" objektum "); out. print (metódusnév); out.println ("(érték);"); out.println ("adja vissza ezt;"); out.println ("}"); out.println ();}); out.println ("}"); }}

7. A példa futtatása

A kódgenerálás működés közbeni megtekintéséhez fordítsa le mindkét modult a közös szülőgyökérből, vagy először fordítsa le a annotation-processzor modul, majd a annotation-user modul.

A generált PersonBuilder osztály megtalálható a annotation-user / target / generated-sources / annotations / com / baeldung / annotation / PersonBuilder.java fájlt, és a következőképpen kell kinéznie:

csomag com.baeldung.annotation; public class PersonBuilder {private Person object = new Person (); public Person build () {return object; } public PersonBuilder setName (java.lang.String érték) {object.setName (érték); adja vissza ezt; } public PersonBuilder setAge (int érték) {object.setAge (érték); adja vissza ezt; }}

8. A processzor regisztrálásának alternatív módjai

Az annotációs processzor használatához a fordítási szakaszban számos más lehetőség áll rendelkezésre, a felhasználási esettől és a használt eszközöktől függően.

8.1. Az Annotation Processor eszköz használata

A találó eszköz egy speciális parancssori segédprogram volt a forrásfájlok feldolgozásához. A Java 5 része volt, de a Java 7 óta elavult a többi opció mellett, és teljesen eltávolította a Java 8-ból. Ez a cikk nem tárgyalja.

8.2. A fordító kulcs használatával

A -processzor A fordító kulcs egy standard JDK eszköz, amely a fordító forrásfeldolgozási szakaszát saját annotációs processzorával egészíti ki.

Vegye figyelembe, hogy magát a processzort és az annotációt már osztályként kell összeállítani egy külön összeállításban, és jelen kell lennie az osztályútvonalon, ezért az első dolog, amit meg kell tennie:

javac com / baeldung / annotation / processzor / BuilderProcessor javac com / baeldung / annotation / processzor / BuilderProperty

Ezután a források tényleges összeállítását a -processzor kulcs az imént összeállított annotációs processzor osztály megadásához:

javac -processzor com.baeldung.annotation.processor.MyProcessor Person.java

Több annotációs processzor megadásához egy menetben vesszőkkel választhatja szét osztályaikat:

javac -processzor package1.Processor1, package2.Processor2 SourceFile.java

8.3. Maven használatával

A maven-compiler-plugin lehetővé teszi az annotációs processzorok megadását a konfiguráció részeként.

Íme egy példa annotációs processzor hozzáadására a fordító pluginhoz. Megadhatja azt a könyvtárat is, ahová a generált forrásokat helyezi, a generatedSourcesDirectory konfigurációs paraméter.

Vegye figyelembe, hogy a BuilderProcessor osztályt már össze kell állítani, például importálni kell egy másik tégelyből a build függőségekben:

   org.apache.maven.plugins maven-compiler-plugin 3.5.1 1.8 1.8 UTF-8 $ {project.build.directory} / generated-sources / com.baeldung.annotation.processor.BuilderProcessor 

8.4. Processzoros üveg hozzáadása az osztályútvonalhoz

Ahelyett, hogy a fordító beállításaiban megadná az annotációs processzort, egyszerűen hozzáadhat egy speciálisan felépített edényt a processzor osztályával a fordító osztályútvonalához.

Az automatikus felvételhez a fordítónak tudnia kell a processzorosztály nevét. Tehát meg kell adnia a META-INF / services / javax.annotation.processing.Processor fájl a processzor teljesen minősített osztályneveként:

com.baeldung.annotation.processor.BuilderProcessor

Megadhat több processzort is ebből az üvegből, hogy automatikusan felvegye őket egy új sorral elválasztva:

csomag1.Processor1 csomag2.Processor2 csomag3.Processzor3

Ha a Maven segítségével építi fel ezt az edényt, és megpróbálja ezt a fájlt közvetlenül a src / main / resources / META-INF / services könyvtárban a következő hibát tapasztalja:

[ERROR] Rossz szolgáltatáskonfigurációs fájl vagy kivétel dobott a processzorobjektum építése közben: javax.annotation.processing.Processor: Szolgáltató com.baeldung.annotation.processor.BuilderProcessor nem található

Ennek oka, hogy a fordító megpróbálja használni ezt a fájlt a forrás-feldolgozás maga a modul szakasza, amikor a BuilderProcessor fájl még nincs lefordítva. A fájlt vagy be kell helyezni egy másik erőforrás könyvtárba, és át kell másolni a META-INF / szolgáltatások könyvtárat a Maven-összeállítás erőforrás-másolási szakaszában, vagy (még jobb) az összeállítás során generált.

A Google autószerviz A következő szakaszban tárgyalt könyvtár lehetővé teszi a fájl egyszerű feljegyzéssel történő létrehozását.

8.5. A Google használata autószerviz Könyvtár

A regisztrációs fájl automatikus létrehozásához használhatja a @AutoService kommentár a Google-tól autószerviz könyvtár, így:

A @AutoService (Processor.class) public BuilderProcessor kiterjeszti az AbstractProcessort {//…}

Ezt az annotációt maga az annotációs processzor dolgozza fel az automatikus szolgáltatási könyvtárból. Ez a processzor generálja a META-INF / services / javax.annotation.processing.Processor fájlt tartalmazó fájl BuilderProcessor osztály név.

9. Következtetés

Ebben a cikkben bemutattuk a forrásszintű kommentárok feldolgozását egy POJO-hoz készítendő Builder osztály létrehozásának példájával. Számos alternatív módot is biztosítottunk az annotációs processzorok regisztrálásához a projektbe.

A cikk forráskódja elérhető a GitHubon.