עיבוד הערות Java ויצירת בונה

1. הקדמה

מאמר זה הוא מבוא לעיבוד הערות ברמת המקור של Java ומספק דוגמאות לשימוש בטכניקה זו ליצירת קבצי מקור נוספים במהלך האוסף.

2. יישומים של עיבוד הערות

עיבוד ההערות ברמת המקור הופיע לראשונה בג'אווה 5. זוהי טכניקה שימושית ליצירת קבצי מקור נוספים בשלב האוסף.

קובצי המקור אינם חייבים להיות קבצי Java - באפשרותך ליצור כל סוג של תיאור, מטה-נתונים, תיעוד, משאבים או כל סוג אחר של קבצים, בהתבסס על הערות בקוד המקור שלך.

עיבוד הערות משמש באופן פעיל בספריות ג'אווה רבות בכל מקום, למשל, ליצירת מטא-משקפיים ב- QueryDSL ו- JPA, לצורך הגדלת שיעורים עם קוד Boilerplate בספריית Lombok.

דבר חשוב לציין הוא המגבלה של ממשק ה- API לעיבוד ההערות - ניתן להשתמש בה רק ליצירת קבצים חדשים, ולא לשינוי הקבצים הקיימים.

היוצא מן הכלל הבולט הוא ספריית Lombok המשתמשת בעיבוד הערות כמנגנון bootstrapping כדי לכלול את עצמה בתהליך האוסף ולשנות את ה- AST באמצעות ממשקי API מהדרים פנימיים. לטכניקה האקית זו אין שום קשר למטרה המיועדת של עיבוד ההערות ולכן היא לא נידונה במאמר זה.

3. ממשק API לעיבוד הערות

עיבוד ההערות נעשה במספר סבבים. כל סיבוב מתחיל בהמהדר מחפש את ההערות בקבצי המקור ובוחר במעבדי ההערות המתאימים להערות אלו. כל מעבד הערות, בתורו, נקרא למקורות המתאימים.

אם נוצרים קבצים כלשהם במהלך תהליך זה, מתחיל סיבוב נוסף עם הקבצים שנוצרו כקלט שלו. תהליך זה נמשך עד שלא נוצרים קבצים חדשים בשלב העיבוד.

כל מעבד הערות, בתורו, נקרא למקורות המתאימים. אם נוצרים קבצים כלשהם במהלך תהליך זה, מתחיל סיבוב נוסף עם הקבצים שנוצרו כקלט שלו. תהליך זה נמשך עד שלא נוצרים קבצים חדשים בשלב העיבוד.

ממשק ה- API לעיבוד ההערות נמצא ב- javax.annotation.processing חֲבִילָה. הממשק העיקרי שתצטרך ליישם הוא ה- מעבד ממשק, שיש לו יישום חלקי בצורה תקציר מעבד מעמד. הכיתה הזו היא זו שאנחנו הולכים להרחיב כדי ליצור מעבד ביאורים משלנו.

4. הגדרת הפרויקט

כדי להדגים את האפשרויות של עיבוד ההערות, נפתח מעבד פשוט ליצירת בוני אובייקטים שוטפים למחלקות המוערות.

אנו הולכים לפצל את הפרויקט שלנו לשני מודולים של Maven. אחד מהם, מעבד הערות המודול, יכיל את המעבד עצמו יחד עם ההערה, ועוד אחד, ה- הערת משתמש המודול, יכיל את המחלקה המבוארת. זהו מקרה שימוש טיפוסי של עיבוד הערות.

ההגדרות עבור מעבד הערות המודול הוא כדלקמן. אנו נשתמש בספריית השירות האוטומטי של גוגל כדי ליצור קובץ מטא-נתונים של מעבד אשר יידונו בהמשך, ו- תוסף maven-compiler מכוון לקוד המקור של Java 8. הגרסאות של תלות אלה חולצות לחלק המאפיינים.

הגרסאות האחרונות של ספריית השירות האוטומטי ותוסף ה- Maven-Compiler נמצאות במאגר Maven Central:

 1.0-rc2 3.5.1 com.google.auto.service שירות אוטומטי $ {auto-service.version} סיפק org.apache.maven.plugins תוסף maven-compiler $ {maven-compiler-plugin.version} 1.8 1.8 

ה הערת משתמש מודול Maven עם המקורות המאושרים אינו זקוק לכוונון מיוחד, למעט הוספת תלות במודול מעבד ההערות בסעיף התלות:

 com.baeldung עיבוד הערות 1.0.0-SNAPSHOT 

5. הגדרת ביאור

נניח שיש לנו שיעור POJO פשוט בשיעור שלנו הערת משתמש מודול עם מספר תחומים:

אדם בכיתה ציבורית {גיל פרטי פרטי; שם מחרוזת פרטי; // גטרים וקובעים ...}

אנו רוצים ליצור כיתת עוזרי בנאי שתחולל את אדם בכיתה בצורה שוטפת יותר:

אדם אדם = PersonBuilder חדש () .setAge (25) .setName ("ג'ון") .build ();

זֶה PersonBuilder הכיתה היא בחירה מובנת מאליה לדור, שכן המבנה שלה מוגדר לחלוטין על ידי אדם שיטות קובע.

בואו ניצור a @ BuilderProperty ביאור ב מעבד הערות מודול לשיטות הקובעים. זה יאפשר לנו לייצר את בּוֹנֶה מחלקה עבור כל מחלקה שהערות שיטות הקבע שלה:

@Target (ElementType.METHOD) @Retention (RetentionPolicy.SOURCE) ציבורי @interface BuilderProperty {}

ה @יַעַד ביאור עם ElementType.METHOD פרמטר מבטיח שניתן להוסיף הערה זו רק על שיטה.

ה מָקוֹר מדיניות שמירה פירושה שביאור זה זמין רק במהלך עיבוד המקור ואינו זמין בזמן הריצה.

ה אדם מחלקה עם נכסים המסומנים עם @ BuilderProperty ההערה תיראה כך:

אדם ממעמד ציבורי {גיל פרטי; שם מחרוזת פרטי; @BuilderProperty בטל ציבורי setAge (int age) {this.age = age; } @BuilderProperty set public name ריק (שם מחרוזת) {this.name = שם; } // גטרס ...}

6. יישום א מעבד

6.1. יצירת תקציר מעבד תת מחלקה

נתחיל בהרחבת ה- תקציר מעבד בכיתה בתוך מעבד הערות מודול Maven.

ראשית, עלינו לציין הערות שהמעבד הזה מסוגל לעבד, וגם את גרסת קוד המקור הנתמכת. ניתן לעשות זאת על ידי יישום השיטות getSupportedAnnotationTypes ו getSupportedSourceVersion של ה מעבד ממשק או על ידי הערת הכיתה שלך עם @SupportedAnnotationTypes ו @SupportedSourceVersion ביאורים.

ה @שירות אוטומטי ההערה היא חלק מה- שירות אוטומטי הספרייה ומאפשר ליצור מטא נתונים של המעבד אשר יוסברו בסעיפים הבאים.

@SupportedAnnotationTypes ("com.baeldung.annotation.processor.BuilderProperty") @SupportedSourceVersion (SourceVersion.RELEASE_8) @AutoService (Processor.class) Class public BuilderProcessor מרחיב את AbstractProcessor {@Override. ; }}

אתה יכול לציין לא רק את שמות כיתות ההערות הקונקרטיות אלא גם תווים כלליים כמו "Com.baeldung.annotation. *" לעבד ביאורים בתוך com.baeldung.annotation חבילה וכל חבילות המשנה שלה, או אפילו “*” לעבד את כל ההערות.

השיטה היחידה שנצטרך ליישם היא תהליך שיטה שעושה את העיבוד עצמו. המהדר מתקשר אליו עבור כל קובץ מקור המכיל את ההערות התואמות.

ביאורים מועברים כראשונים הגדר הערות והמידע על סבב העיבוד הנוכחי מועבר כ- RoundEnviroment roundEnv טַעֲנָה.

השיבה בוליאני ערך צריך להיות נָכוֹן אם מעבד ההערות שלך עיבד את כל ההערות שהועברו, ואינך מעוניין שהן יועברו למעבדי הערות אחרים ברשימה.

6.2. איסוף נתונים

המעבד שלנו עדיין לא באמת עושה שום דבר שימושי, אז בואו נמלא אותו בקוד.

ראשית, נצטרך לחזור על כל סוגי ההערות שנמצאים בכיתה - במקרה שלנו, ה- ביאורים להגדיר יהיה אלמנט יחיד המתאים ל- @ BuilderProperty ביאור, גם אם הערה זו מתרחשת מספר פעמים בקובץ המקור.

ובכל זאת, עדיף ליישם את תהליך שיטה כמעגל איטרציה, לשם השלמה:

@ ביטול תהליך בוליאני ציבורי (הגדר הערות, RoundEnvironment roundEnv) {עבור (ביאור TypeElement: ביאורים) {Set annotatedElements = roundEnv.getElementsAnnotatedWith (ביאור); // ...} להחזיר נכון; }

בקוד זה אנו משתמשים ב- סביבה עגולה למשל כדי לקבל את כל האלמנטים המבוארים עם ה- @ BuilderProperty ביאור. במקרה של אדם אלמנטים אלה תואמים את setName ו setAge שיטות.

@ BuilderProperty משתמש ההערה יכול לטעות בטעות בשיטות שאינן מגדירות. שם שיטת הקובע צריך להתחיל ב מַעֲרֶכֶת, והשיטה צריכה לקבל טיעון יחיד. אז בואו נפריד בין החיטה למוץ.

בקוד הבא אנו משתמשים ב- Collectors.partitioningBy () אספן לפצל שיטות ביאור לשני אוספים: קובעי הערות נכונים ושיטות אחרות שהוסרו בטעות:

מַפָּה annotatedMethods = annotatedElements.stream (). collect (Collectors.partitioningBy (element -> ((ExecutableType) element.asType ()). getParameterTypes (). size () == 1 && element.getSimpleName (). toString (). startsWith ("מַעֲרֶכֶת"))); קובעי רשימות = annotatedMethods.get (נכון); רשימת otherMethods = annotatedMethods.get (false);

כאן אנו משתמשים ב- Element.asType () שיטה לקבל מופע של TypeMirror מחלקה אשר נותנת לנו יכולת להתבונן פנימה על סוגים למרות שאנחנו רק בשלב עיבוד המקור.

עלינו להזהיר את המשתמש מפני שיטות שהושגו באופן שגוי, אז בואו נשתמש ב- מסגר מופע נגיש מה AbstractProcessor.processingEnv שדה מוגן. השורות הבאות יפיקו שגיאה עבור כל אלמנט שהושג בטעות בשלב עיבוד המקור:

otherMethods.forEach (element -> processingEnv.getMessager (). printMessage (Diagnostic.Kind.ERROR, "@BuilderProperty חייב להיות מיושם על שיטת setXxx" + "עם ארגומנט יחיד", אלמנט));

כמובן שאם אוסף הקובעים הנכון ריק, אין טעם להמשיך באיטרציה של קבוצת האלמנטים הנוכחית:

אם (setters.isEmpty ()) {המשך; }

אם לאוסף הקובעים יש לפחות אלמנט אחד, אנו נשתמש בו בכדי לקבל את שם המחלקה המוסמך במלואו מהאלמנט הסוגר, שבמקרה של שיטת הקובע נראה כמחלקת המקור עצמה:

מחרוזת className = ((TypeElement) setters.get (0) .getEnclosingElement ()). GetQualifiedName (). ToString ();

החלק האחרון של המידע הדרוש לנו כדי ליצור מחלקה לבונה הוא מפה בין שמות המגדירים ושמות סוגי הארגומנטים שלהם:

מפה setterMap = setters.stream (). Collect (Collectors.toMap (setter -> setter.getSimpleName (). ToString (), setter -> ((ExecutableType) setter.asType ()). GetParameterTypes (). Get (0) .toString ()));

6.3. יצירת קובץ הפלט

כעת יש לנו את כל המידע הדרוש לנו כדי ליצור מחלקת בונה: שם מחלקת המקור, כל שמות הקובעים שלו וסוגי הטיעון שלהם.

כדי ליצור את קובץ הפלט, נשתמש ב- פילר מופע המסופק שוב על ידי האובייקט ב- AbstractProcessor.processingEnv רכוש מוגן:

JavaFileObject builderFile = processingEnv.getFiler () .createSourceFile (builderClassName); נסה (PrintWriter out = New PrintWriter (builderFile.openWriter ())) {// כותב קובץ שנוצר כדי לצאת ...}

הקוד השלם של writeBuilderFile השיטה מובאת להלן. עלינו לחשב רק את שם החבילה, שם כיתת הבונה המוסמכת לחלוטין ושמות מחלקות פשוטים עבור מחלקת המקור וכיתת הבונה. שאר הקוד די פשוט.

חלל פרטי writeBuilderFile (String className, Map setterMap) זורק IOException {String packageName = null; int lastDot = className.lastIndexOf ('.'); אם (lastDot> 0) {packageName = className.substring (0, lastDot); } מחרוזת simpleClassName = className.substring (lastDot + 1); מחרוזת builderClassName = className + "Builder"; מחרוזת builderSimpleClassName = builderClassName. Substring (lastDot + 1); JavaFileObject builderFile = processingEnv.getFiler () .createSourceFile (builderClassName); נסה (PrintWriter out = New PrintWriter (builderFile.openWriter ())) {if (packageName! = null) {out.print ("package"); out.print (packageName); out.println (";"); out.println (); } out.print ("מחלקה ציבורית"); out.print (builderSimpleClassName); out.println ("{"); out.println (); out.print ("פרטי"); out.print (simpleClassName); out.print ("object = new"); out.print (simpleClassName); out.println ("();"); out.println (); out.print ("ציבורי"); out.print (simpleClassName); out.println ("build () {"); out.println ("החזר אובייקט;"); out.println ("}"); out.println (); setterMap.entrySet (). forEach (setter -> {String methodName = setter.getKey (); ArgumentType = setter.getValue (); out.print ("public"); out.print (builderSimpleClassName); out.print ( ""); out.print (methodName); out.print ("("); out.print (argumentType); out.println ("value) {"); out.print ("object."); out. הדפס (methodName); out.println ("(ערך);"); out.println ("להחזיר את זה;"); out.println ("}"); out.println ();}); out.println ("}"); }}

7. הפעלת הדוגמא

כדי לראות את יצירת הקוד בפעולה, עליך לאסוף את שני המודולים משורש האב המשותף או קודם להדר את ה- מעבד הערות המודול ואז ה- הערת משתמש מודול.

הנוצר PersonBuilder ניתן למצוא את הכיתה בתוך ביאור-משתמש / יעד / מקורות שנוצרו / הערות / com / baeldung / ביאור / PersonBuilder.java צריך להיראות כך:

חבילה com.baeldung.annotation; מחלקה ציבורית PersonBuilder {אובייקט אדם פרטי = אדם חדש (); בניית אדם ציבורי () אובייקט החזרה; } setName PersonBuilder ציבורי (java.lang.String value) {object.setName (value); להחזיר את זה; } PersonBuilder ציבורי setAge (ערך int) {object.setAge (value); להחזיר את זה; }}

8. דרכים חלופיות לרישום מעבד

כדי להשתמש במעבד ההערות שלך במהלך שלב האוסף, יש לך כמה אפשרויות אחרות, תלוי במקרה השימוש שלך והכלים שבהם אתה משתמש.

8.1. באמצעות כלי מעבד ההערות

ה מַתְאִים הכלי היה כלי שורת פקודה מיוחד לעיבוד קבצי מקור. זה היה חלק מג'אווה 5, אך מכיוון שג'אווה 7 הוצא משימוש לטובת אפשרויות אחרות והוסר לחלוטין בג'אווה 8. זה לא יידון במאמר זה.

8.2. באמצעות מקש המהדר

ה -מעבד מפתח המהדר הוא מתקן JDK סטנדרטי להגדלת שלב עיבוד המקור של המהדר עם מעבד ההערות שלך.

שים לב כי המעבד עצמו וההערה צריכים כבר להיות מורכבים כשיעורים באוסף נפרד ולהימצא על מסלול הכיתה, לכן הדבר הראשון שעליך לעשות הוא:

javac com / baeldung / הערה / מעבד / Builder מעבד javac com / baeldung / ביאור / מעבד / BuilderProperty

ואז אתה עושה את הידור בפועל של המקורות שלך עם -מעבד מקש המציין את מחלקת מעבד ההערות שהרכבת זה עתה:

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

כדי לציין מספר מעבדי הערות בבת אחת, אתה יכול להפריד בין שמות המחלקות שלהם בפסיקים, כך:

javac - מעבד חבילה 1. מעבד 1, חבילה 2. מעבד 2 SourceFile.java

8.3. באמצעות Maven

ה תוסף maven-compiler מאפשר ציון מעבדי הערות כחלק מתצורתו.

הנה דוגמה להוספת מעבד הערות עבור תוסף המהדר. אתה יכול גם לציין את הספרייה שאליה להכניס מקורות שנוצרו באמצעות generatedSourcesDirectory פרמטר תצורה.

שים לב שה- BuilderProcessor יש כבר להרכיב את הכיתה, למשל, לייבא מצנצנת אחרת בתלות הבנייה:

   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. הוספת צנצנת מעבד ל- Classpath

במקום לציין את מעבד ההערות באפשרויות המהדר, תוכל פשוט להוסיף צנצנת מובנית במיוחד עם מחלקת המעבדים לשביל הכיתה של המהדר.

כדי להרים אותו באופן אוטומטי, על המהדר לדעת את שם מחלקת המעבדים. אז אתה צריך לציין את זה ב- META-INF / services / javax.annotation.processing.Processor קובץ כשם מחלקה מלא של המעבד:

com.baeldung.annotation.processor.BuilderProcessor

אתה יכול גם לציין מספר מעבדים מהצנצנת הזו לאיסוף אוטומטי על ידי הפרדתם עם שורה חדשה:

חבילה 1. מעבד 1 חבילה 2. מעבד 2 חבילה 3. מעבד 3

אם אתה משתמש ב- Maven כדי לבנות את הצנצנת הזו ומנסה להכניס את הקובץ ישירות אל ה- src / main / resources / META-INF / services בספרייה, תיתקל בשגיאה הבאה:

[שגיאה] קובץ תצורת שירות שגוי, או חריג שנזרק בעת בניית אובייקט המעבד: javax.annotation.processing.Processor: ספק com.baeldung.annotation.processor.BuilderProcessor לא נמצא

הסיבה לכך היא שהמהדר מנסה להשתמש בקובץ זה במהלך ה- עיבוד מקור שלב המודול עצמו כאשר ה- BuilderProcessor הקובץ עדיין לא נערך. יש להכניס את הקובץ לספריית משאבים אחרת ולהעתיק אותו אל META-INF / שירותים הספרייה בשלב העתקת המשאבים של בניית Maven, או (אפילו יותר טוב) שנוצר במהלך הבנייה.

הגוגל שירות אוטומטי הספרייה, הנדונה בסעיף הבא, מאפשרת ליצור קובץ זה באמצעות הערה פשוטה.

8.5. באמצעות גוגל שירות אוטומטי סִפְרִיָה

כדי ליצור את קובץ הרישום באופן אוטומטי, אתה יכול להשתמש ב- @שירות אוטומטי הערה מה- Google שירות אוטומטי ספרייה, כך:

@AutoService (Processor.class) BuilderProcessor הציבורי מרחיב את AbstractProcessor {//…}

הערה זו מעובדת בעצמה על ידי מעבד ההערות מספריית השירות האוטומטי. מעבד זה מייצר את META-INF / services / javax.annotation.processing.Processor קובץ המכיל את BuilderProcessor שם הכיתה.

9. מסקנה

במאמר זה הדגמנו עיבוד הערות ברמת המקור באמצעות דוגמה ליצירת כיתת Builder עבור POJO. סיפקנו גם מספר דרכים חלופיות לרישום מעבדי הערות בפרויקט שלך.

קוד המקור של המאמר זמין ב- GitHub.


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