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.