מבוא ל- CDI (Contexts and Dependency Injection) ב- Java
1. סקירה כללית
CDI (Contexts and Dependency Injection) היא מסגרת סטנדרטית של הזרקת תלות הכלולה ב- Java EE 6 ומעלה.
זה מאפשר לנו לנהל את מחזור החיים של רכיבים סטטיים באמצעות הקשרים של מחזור חיים ספציפי לתחום ולהזריק רכיבים (שירותים) לאובייקטים של לקוחות בצורה בטיחותית.
במדריך זה נבחן לעומק את התכונות הרלוונטיות ביותר של CDI וניישם גישות שונות להזרקת תלות בשיעורי לקוח.
2. DYDI (הזרקת תלות עשה זאת בעצמך)
בקצרה, ניתן ליישם את DI מבלי להזדקק למסגרת כלשהי.
גישה זו ידועה בכינויו DYDI (הזרקת תלות עשה זאת בעצמך).
עם DYDI, אנו שומרים על קוד יישום מבודד מיצירת אובייקטים על ידי העברת התלות הנדרשת למחלקות הלקוחות דרך מפעלים / בונים פשוטים.
כך יכול להיראות יישום DYDI בסיסי:
ממשק ציבורי TextService {String doSomethingWithText (טקסט מחרוזת); מחרוזת doSomethingElseWithText (טקסט מחרוזת); }
מחלקה ציבורית SpecializedTextService מיישמת את TextService {...}
מחלקה ציבורית TextClass {private TextService textService; // בונה}
class class TextClassFactory {public TextClass getTextClass () {להחזיר TextClass חדש (SpecializedTextService חדש ();}}
כמובן ש- DYDI מתאים לכמה מקרי שימוש פשוטים יחסית.
אם יישום הדוגמה שלנו גדל בגודל ובמורכבותו, ומיישם רשת גדולה יותר של אובייקטים מקושרים זה לזה, בסופו של דבר אנו מזהמים אותה בטונות של מפעלי גרפי עצמים.
זה ידרוש הרבה קוד boilerplate רק ליצירת גרפים של אובייקטים. זה לא פיתרון שניתן להרחבה לחלוטין.
האם אנו יכולים לעשות די טוב יותר? כמובן שאנחנו יכולים. כאן בדיוק CDI נכנס לתמונה.
3. דוגמה פשוטה
CDI הופך את DI לתהליך לא פשוט, שהסתכם בקישוט שיעורי השירות בכמה הערות פשוטות, והגדרת נקודות ההזרקה המתאימות בשיעורי הלקוח.
כדי להציג כיצד CDI מיישם את DI ברמה הבסיסית ביותר, נניח שאנחנו רוצים לפתח יישום פשוט לעריכת קבצי תמונה. מסוגל לפתוח, לערוך, לכתוב, לשמור קובץ תמונה וכן הלאה.
3.1. ה "שעועית.קסמל" קוֹבֶץ
ראשית, עלינו להציב א "שעועית.קסמל" קובץ ב- "Src / main / resources / META-INF /" תיקיה. גם אם קובץ זה אינו מכיל כלל הוראות DI ספציפיות, הוא נדרש להפעלת CDI:
3.2. שיעורי השירות
לאחר מכן, בואו ניצור את מחלקות השירות המבצעות את הקובץ שהוזכר לעיל בביצועי GIF, JPG ו- PNG:
ממשק ציבורי ImageFileEditor {String openFile (String fileName); מחרוזת editFile (מחרוזת fileName); String writeFile (String fileName); מחרוזת saveFile (מחרוזת fileName); }
מחלקה ציבורית GifFileEditor מיישמת את ImageFileEditor {@Override public String openFile (String fileName) {return "פתיחת קובץ GIF" + fileName; } @ עקוף ציבורי מחרוזת editFile (String fileName) {החזר "עריכת קובץ GIF" + fileName; } @Override מחרוזת writeFile (מחרוזת קובץ שם) {החזר "כתיבת קובץ GIF" + קובץ שם; } @ עקוף ציבורי String saveFile (String fileName) {החזר "שמירת קובץ GIF" + fileName; }}
מחלקה ציבורית JpgFileEditor מיישמת ImageFileEditor {// יישומים ספציפיים ל- JPG עבור openFile () / editFile () / writeFile () / saveFile () ...}
מחלקה ציבורית PngFileEditor מיישמת ImageFileEditor {// יישומים ספציפיים PNG עבור openFile () / editFile () / writeFile () / saveFile () ...}
3.3. מחלקת הלקוחות
לבסוף, בואו נשתמש בכיתת לקוח שלוקחת ImageFileEditor יישום בבנאי, ובואו נגדיר נקודת הזרקה עם ה- @לְהַזרִיק ביאור:
מחלקה ציבורית ImageFileProcessor {private ImageFileEditor imageFileEditor; @ הזריק ImageFileProcessor ציבורי (ImageFileEditor imageFileEditor) {this.imageFileEditor = imageFileEditor; }}
במילים פשוטות, ה @לְהַזרִיק ביאור הוא סוס העבודה הממשי של CDI. זה מאפשר לנו להגדיר נקודות הזרקה בכיתות הלקוח.
במקרה הזה, @לְהַזרִיק מורה ל- CDI להזריק ImageFileEditor יישום בבנאי.
יתר על כן, ניתן גם להזריק שירות באמצעות ה- @לְהַזרִיק ביאור בשדות (הזרקת שדה) וקובעים (הזרקת סטרים). נבחן אפשרויות אלה בהמשך.
3.4. בניית ה ImageFileProcessor גרף אובייקט עם ריתוך
כמובן, עלינו לוודא ש- CDI יזריק את הזכות ImageFileEditor יישום לתוך ImageFileProcessor בונה כיתתי.
לשם כך, ראשית, עלינו לקבל מופע של הכיתה.
מכיוון שלא נסמוך על שום שרת יישומי Java EE לשימוש ב- CDI, נעשה זאת עם Weld, יישום הפניה ל- CDI ב- Java SE:
סטטי ציבורי ריק ריק (String [] args) {Weld weld = New Weld (); מיכל WeldContainer = weld.initialize (); ImageFileProcessor imageFileProcessor = container.select (ImageFileProcessor.class) .get (); System.out.println (imageFileProcessor.openFile ("file1.png")); container.shutdown (); }
הנה, אנו יוצרים מיכל ריתוך אובייקט, ואז מקבל ImageFileProcessor אובייקט, ולבסוף קורא לו תיק פתוח() שיטה.
כצפוי, אם נפעיל את היישום, CDI תתלונן בקול רם על ידי השלכת א חריג פריסה:
תלות לא מרוצה מהסוג ImageFileEditor עם מוקדמות @ ברירת המחדל בנקודת ההזרקה ...
אנו מקבלים את החריג הזה מכיוון ש- CDI לא יודע מה ImageFileEditor יישום כדי להזריק לתוך ImageFileProcessor בַּנַאִי.
במינוח של CDI, זה ידוע כחריג הזרקה דו משמעי.
3.5. ה @בְּרִירַת מֶחדָל ו @חֲלוּפָה ביאורים
קל לפתור את העמימות הזו. CDI, כברירת מחדל, מציין את כל היישומים של ממשק עם ה- @בְּרִירַת מֶחדָל ביאור.
אז עלינו לומר לו במפורש איזו יישום צריך להזריק למחלקת הלקוחות:
@ בכיתה ציבורית אלטרנטיבית GifFileEditor מיישמת את ImageFileEditor {...}
@Alternative public class JpgFileEditor מיישם את ImageFileEditor {...}
PngFileEditor בכיתה ציבורית מיישם את ImageFileEditor {...}
במקרה זה הערנו GifFileEditor ו JpgFileEditor עם ה @חֲלוּפָה ביאור, אז CDI עכשיו יודע את זה PngFileEditor (מצוין כברירת מחדל עם @בְּרִירַת מֶחדָל ביאור) הוא היישום שאנו רוצים להזריק.
אם נפעיל מחדש את היישום, הפעם היא תבוצע כצפוי:
פתיחת קובץ PNG file1.png
יתר על כן, ביאור PngFileEditor עם ה @בְּרִירַת מֶחדָל ביאור ושמירת היישומים האחרים כחלופות, יניבו את אותה התוצאה לעיל.
זה מראה, בקצרה, כיצד אנו יכולים להחליף בקלות הזרקת יישומים בזמן הריצה פשוט על ידי החלפת @חֲלוּפָה ביאורים בשיעורי השירות.
4. הזרקת שדה
CDI תומך בהזרקה בשטח וגם במגדיר מהקופסה.
כך לבצע הזרקת שדה (הכללים לשירותים מוסמכים עם @בְּרִירַת מֶחדָל ו @חֲלוּפָה ההערות נשארות זהות):
@Inject פרטי פרטי ImageFileEditor imageFileEditor;
5. הזרקת סתר
באופן דומה, הנה כיצד לבצע הזרקת סטר:
@ Inject public void setImageFileEditor (ImageFileEditor imageFileEditor) {...}
6. ה @ שמות ביאור
עד כה למדנו כיצד להגדיר נקודות הזרקה בכיתות לקוחות ולהזריק שירותים עם ה- @לְהַזרִיק, @בְּרִירַת מֶחדָל , ו @חֲלוּפָה ביאורים, המכסים את מרבית מקרי השימוש.
עם זאת, CDI מאפשר לנו גם לבצע הזרקת שירות עם ה- @ שמות ביאור.
שיטה זו מספקת דרך סמנטית יותר של הזרקת שירותים, על ידי קשירת שם משמעות ליישום:
@Named ("GifFileEditor") מחלקה ציבורית GifFileEditor מיישמת את ImageFileEditor {...} @Named ("JpgFileEditor") JpgFileEditor ממחישה ציבורית מיישמת את ImageFileEditor {...} @Named ("PngFileEditor") מחלקה ציבורית PngFileEditor מיישמת את ImageFileEditor ... }
כעת, עלינו לשקם מחדש את נקודת ההזרקה ב- ImageFileProcessor בכיתה כדי להתאים ליישום בשם:
@Inject ImageFileProcessor ציבורי (@Named ("PngFileEditor") ImageFileEditor imageFileEditor) {...}
אפשר גם לבצע הזרקת שדה ומגדיר עם יישומים בשם, שנראים דומים מאוד לשימוש ב- @בְּרִירַת מֶחדָל ו @חֲלוּפָה ביאורים:
@Inject private final @Named ("PngFileEditor") ImageFileEditor imageFileEditor; @Inject public void setImageFileEditor (@Named ("PngFileEditor") ImageFileEditor imageFileEditor) {...}
7. ה @Produces ביאור
לפעמים שירות דורש אתחול מלא של תצורה כלשהי לפני שהוא מוזרק לטיפול בתלות נוספות.
CDI מספק תמיכה במצבים אלה, דרך @Produces ביאור.
@Produces מאפשר לנו ליישם שיעורי מפעל, שאחריותם היא יצירת שירותי אתחול מלא.
כדי להבין איך @Produces ביאור עובד, בואו לשקף את ה ImageFileProcessor בכיתה, כך שזה יכול לקחת תוספת TimeLogger שירות בקונסטרוקטור.
השירות ישמש לרישום הזמן בו מתבצעת פעולת קובץ תמונה מסוימת:
@Inject PublicFileProcessor (ImageFileEditor imageFileEditor, TimeLogger timeLogger) {...} ציבורי מחרוזת openFile (String fileName) {return imageFileEditor.openFile (fileName) + "at:" + timeLogger.getTime (); } // שיטות קובץ תמונה נוספות
במקרה זה, TimeLogger השיעור לוקח שני שירותים נוספים, SimpleDateFormat ו לוּחַ שָׁנָה:
מחלקה ציבורית TimeLogger {פרטי SimpleDateFormat dateFormat; לוח שנה לוח שנה פרטי; // בוני ציבורי מחרוזת getTime () {return dateFormat.format (calendar.getTime ()); }}
כיצד נגיד ל- CDI היכן עלינו לבדוק כיצד לבצע אתחול מלא TimeLogger לְהִתְנַגֵד?
אנחנו פשוט יוצרים a TimeLogger בכיתת מפעל והערת את שיטת המפעל שלה עם @Produces ביאור:
מחלקה ציבורית TimeLoggerFactory {@Produces TimeLogger public getTimeLogger () {להחזיר TimeLogger חדש (SimpleDateFormat חדש ("HH: mm"), Calendar.getInstance ()); }}
בכל פעם שאנחנו מקבלים ImageFileProcessor למשל, CDI יסרוק את TimeLoggerFactory ואז לקרוא ל- getTimeLogger () שיטה (כפי שהיא ביאור עם @Produces ביאור), ולבסוף מזריקים את לוגר זמן שֵׁרוּת.
אם אנו מריצים את יישום הדגימה המחודש עם לְרַתֵך, זה יפיק את הדברים הבאים:
פתיחת קובץ PNG file1.png בשעה: 17:46
8. מוקדמות בהתאמה אישית
CDI תומך בשימוש במוקדמות מותאמות אישית לתלות מתאימה ולפתרון נקודות הזרקה מעורפלות.
מוקדמות מותאמות אישית הן תכונה חזקה מאוד. הם לא רק קושרים שם סמנטי לשירות, אלא הם קושרים גם מטא נתונים להזרקה. מטא-נתונים כגון RetentionPolicy ויעדי ההערות המשפטיות (ElementType).
בואו נראה כיצד להשתמש במוקדמות מותאמות אישית ביישום שלנו:
@Qualifier @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER}) ציבורי @interface GifFileEditorQualifier {}
@Qualifier @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER}) public @interface JpgFileEditorQualifier {}
@Qualifier @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER}) ציבורי @interface PngFileEditorQualifier {}
עכשיו, בואו נקשר את המוקדמות המותאמות אישית ל ImageFileEditor יישומים:
@GifFileEditorQualifier מחלקה ציבורית GifFileEditor מיישם את ImageFileEditor {...}
@JpgFileEditorQualifier מחלקה ציבורית JpgFileEditor מיישם את ImageFileEditor {...}
@PngFileEditorQualifier מחלקה ציבורית PngFileEditor מיישם את ImageFileEditor {...}
לבסוף, בואו לשקם מחדש את נקודת ההזרקה ב- ImageFileProcessor מעמד:
@Inject ImageFileProcessor ציבורי (@PngFileEditorQualifier ImageFileEditor imageFileEditor, TimeLogger timeLogger) {...}
אם אנו מריצים את היישום שלנו שוב, הוא אמור לייצר את אותה הפלט המוצגת לעיל.
מוקדמות מותאמות אישית מספקות גישה סמנטית מסודרת לכריכת שמות ונתוני הערות ליישומים.
בנוסף, מוקדמות מותאמות אישית מאפשרות לנו להגדיר נקודות הזרקה מגבילות יותר מסוג בטיחות (בהשוואה לפונקציונליות של ההערות @Default ו- @Alternative).
אם רק תת-סוג מוסמך בהיררכיית סוגים, אז CDI יזריק רק את תת-הסוג, ולא את סוג הבסיס.
9. מסקנה
ללא עוררין, CDI הופך הזרקת תלות למאודעלות ההערות הנוספות היא מעט מאוד מאמץ להשגת הזרקת תלות מאורגנת.
ישנם מקרים בהם ל- DYDI עדיין מקומו על פני CDI. כמו בעת פיתוח יישומים פשוטים למדי המכילים רק גרפי עצמים פשוטים.
כמו תמיד, כל דגימות הקוד המוצגות במאמר זה זמינות ב- GitHub.