Proxies דינמיים בג'אווה

1. הקדמה

מאמר זה עוסק בפרוקסי דינמיות של Java - שהוא אחד ממנגנוני ה- proxy העיקריים העומדים לרשותנו בשפה.

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

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

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

תכונה זו מובנית ב- JDK הסטנדרטי, ולכן אין צורך בתלות נוספת.

2. מטפל בקריאה

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

ראשית, עלינו ליצור תת-סוג של java.lang.reflect.InvocationHandler:

מחלקה ציבורית DynamicInvocationHandler מיישמת את InvocationHandler {לוגר סטטי פרטי LOGGER = LoggerFactory.getLogger (DynamicInvocationHandler.class); @ הפעל אובייקט ציבורי להפעלה (פרוקסי אובייקט, שיטת שיטה, אובייקט [] טענות) זורק את {LOGGER.info ("שיטה שהוזעקה: {}", method.getName ()) זורק חזרה 42; }}

כאן הגדרנו פרוקסי פשוט המתעד איזו שיטה הופעלה ומחזיר 42.

3. יצירת מופע פרוקסי

מופע פרוקסי שמטופל על ידי מטפל ההזמנה שהגדרנו זה עתה נוצר באמצעות קריאת שיטת מפעל ב- java.lang.reflect.Proxy מעמד:

מפה proxyInstance = (מפה) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), מחלקה חדשה [] {Map.class}, DynamicInvocationHandler חדש ());

ברגע שיש לנו מופע proxy אנו יכולים להפעיל את שיטות הממשק שלו כרגיל:

proxyInstance.put ("שלום", "עולם");

כצפוי הודעה על לָשִׂים() השיטה בה מופעלת מודפסת בקובץ היומן.

4. מטפל בקריאה באמצעות ביטויים למבדה

מאז InvocationHandler הוא ממשק פונקציונלי, ניתן להגדיר את המטפל בשורה באמצעות ביטוי למבדה:

מפה proxyInstance = (מפה) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), מחלקה חדשה [] {Map.class}, (proxy, method, methodArgs) -> {if (method.getName (). שווה ("get ")) {לחזור 42;} אחרת {זרוק UnsupportedOperationException חדש (" שיטה לא נתמכת: "+ method.getName ());}});

כאן הגדרנו מטפל שמחזיר 42 לכל פעולות ה- get ו- throw לא נתמךOperationException לכל השאר.

הוא מופעל באותו אופן בדיוק:

(int) proxyInstance.get ("שלום"); // 42 proxyInstance.put ("שלום", "עולם"); // יוצא מן הכלל

5. דוגמה לתזמון פרוקסי דינמי

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

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

מחלקה ציבורית TimingDynamicInvocationHandler מיישם את InvocationHandler {לוגר סטטי פרטי LOGGER = LoggerFactory.getLogger (TimingDynamicInvocationHandler.class); שיטות מפות סופיות פרטיות = HashMap חדש (); יעד אובייקט פרטי; Public TimingDynamicInvocationHandler (יעד אובייקט) {this.target = target; עבור (שיטת שיטה: target.getClass (). getDeclaredMethods ()) {this.methods.put (method.getName (), שיטה); }} @Override הפעל אובייקט ציבורי (Proxy Object, שיטת שיטה, Object [] args) זורק Throwable {long start = System.nanoTime (); תוצאת אובייקט = methods.get (method.getName ()). הפעל (target, args); זמן רב חלף = System.nanoTime () - התחל; LOGGER.info ("ביצוע {} הסתיים ב- {} ns", method.getName (), חלף); תוצאת החזרה; }}

לאחר מכן, ניתן להשתמש ב- proxy זה על סוגי אובייקטים שונים:

Map mapProxyInstance = (Map) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), מחלקה חדשה [] {Map.class}, TimingDynamicInvocationHandler חדש (HashMap חדש ())); mapProxyInstance.put ("שלום", "עולם"); CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance (DynamicProxyTest.class.getClassLoader (), מחלקה חדשה [] {CharSequence.class}, TimingDynamicInvocationHandler חדש ("שלום עולם")); csProxyInstance.length ()

כאן צירפנו מפה ורצף אותיות (מחרוזת).

הפעלות על שיטות ה- proxy יאצלו לאובייקט העטוף וכן יניבו הצהרות רישום:

ביצוע הושלם בשנת 19153 ns ביצוע הושלם בשנת 8891 ns ביצוע charAt הסתיים בשנת 11152 ns אורך ביצוע סיים בשנת 10087 ns

6. מסקנה

במדריך מהיר זה, בחנו את הפרוקסי הדינמיים של ג'אווה וכן כמה מהשימושים האפשריים בה.

כמו תמיד, ניתן למצוא את הקוד בדוגמאות ב- GitHub.