CodeGym /בלוג Java /Random-HE /פרוקסי דינמיים ב-Java
John Squirrels
רָמָה
San Francisco

פרוקסי דינמיים ב-Java

פורסם בקבוצה
היי! היום נשקול נושא חשוב ומעניין למדי: יצירת מחלקות פרוקסי דינמיות בג'אווה. זה לא מאוד פשוט, אז ננסה להבין את זה באמצעות דוגמאות :) אז, השאלה הכי חשובה: מה הם פרוקסי דינמיים ולמה הם מיועדים? מחלקה פרוקסי היא מעין "תוסף" על גבי המחלקה המקורית, המאפשרת לנו לשנות את התנהגות המחלקה המקורית במידת הצורך. מה זה אומר "לשנות התנהגות" ואיך זה עובד? שקול דוגמה פשוטה. נניח שיש לנו ממשק Person ומחלקה פשוטה של ​​Man שמיישמת את הממשק הזה
public interface Person {

   public void introduce(String name);

   public void sayAge(int age);

   public void sayWhereFrom(String city, String country);
}

public class Man implements Person {

   private String name;
   private int age;
   private String city;
   private String country;

   public Man(String name, int age, String city, String country) {
       this.name = name;
       this.age = age;
       this.city = city;
       this.country = country;
   }

   @Override
   public void introduce(String name) {

       System.out.println("My name is " + this.name);
   }

   @Override
   public void sayAge(int age) {
       System.out.println("I am " + this.age + " years old");
   }

   @Override
   public void sayWhereFrom(String city, String country) {

       System.out.println("I'm from " + this.city + ", " + this.country);
   }

   // ...getters, setters, etc.
}
למחלקת ה-Man שלנו יש 3 שיטות: introduce, sayAge ו- sayWhereFrom. תארו לעצמכם שקיבלנו את המחלקה הזו כחלק מספריית JAR מהמדף ואנחנו לא יכולים פשוט לשכתב את הקוד שלה. אבל אנחנו צריכים גם לשנות את ההתנהגות שלו. לדוגמה, אנחנו לא יודעים איזו שיטה עשויה להיקרא על האובייקט שלנו, אבל אנחנו רוצים שהאדם שלנו יגיד "היי!" (אף אחד לא אוהב מישהו שהוא לא מנומס) כשכל אחת מהשיטות נקראת. פרוקסי דינמיים - 2מה עלינו לעשות במצב זה? נצטרך כמה דברים:
  1. InvocationHandler

מה זה? InvocationHandler הוא ממשק מיוחד המאפשר לנו ליירט כל קריאת שיטה לאובייקט שלנו ולהוסיף את ההתנהגות הנוספת שאנו צריכים. אנחנו צריכים ליצור מיירט משלנו, כלומר ליצור מחלקה שמיישמת את הממשק הזה. זה די פשוט:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PersonInvocationHandler implements InvocationHandler {

private Person person;

public PersonInvocationHandler(Person person) {
   this.person = person;
}

 @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

       System.out.println("Hi!");
       return null;
   }
}
עלינו ליישם רק שיטת ממשק אחת: invoke() . ודרך אגב, הוא עושה את מה שאנחנו צריכים: הוא מיירט את כל קריאות השיטה לאובייקט שלנו ומוסיף את ההתנהגות הדרושה (בתוך שיטת invoke() אנו מוציאים "Hi!" לקונסולה).
  1. החפץ המקורי ונציגיו.
אנו יוצרים את אובייקט ה-Man המקורי שלנו ו"תוסף" (פרוקסי) עבורו:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       // Create the original object
       Man arnold = new Man("Arnold", 30, "Thal", "Austria");

       // Get the class loader from the original object
       ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();

       // Get all the interfaces that the original object implements
       Class[] interfaces = arnold.getClass().getInterfaces();

       // Create a proxy for our arnold object
       Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

       // Call one of our original object's methods on the proxy object
       proxyArnold.introduce(arnold.getName());

   }
}
זה לא נראה פשוט במיוחד! הוספתי במיוחד הערה עבור כל שורת קוד. בואו נסתכל מקרוב על מה שקורה. בשורה הראשונה אנחנו פשוט יוצרים את האובייקט המקורי שעבורו ניצור פרוקסי. שתי השורות הבאות עשויות לגרום לך לקושי:
// Get the class loader from the original object
ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();

// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();
למעשה, לא קורה כאן שום דבר מיוחד :) בשורה הרביעית, אנו משתמשים במחלקה המיוחדת של Proxy ובשיטת newProxyInstance() הסטטית שלה :
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
שיטה זו רק יוצרת את אובייקט ה-proxy שלנו. אנו מעבירים לשיטה את המידע על המחלקה המקורית, שקיבלנו בשלב האחרון ( ClassLoader שלה ורשימת הממשקים שלה), וכן את האובייקט InvocationHandler שנוצר בעבר. העיקר לא לשכוח להעביר את חפץ ארנולד המקורי שלנו למטפל ב-invocation, אחרת לא יהיה מה "לטפל" :) עם מה יצא לנו? כעת יש לנו אובייקט פרוקסי: proxyArnold . זה יכול לקרוא לכל שיטות של ממשק האדם . למה? כי נתנו לו רשימה של כל הממשקים כאן:
// Get all the interfaces that the original object implements
Class[] interfaces = arnold.getClass().getInterfaces();

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
עכשיו הוא יודע על כל השיטות של ממשק האדם . בנוסף, העברנו ל-proxy שלנו אובייקט PersonInvocationHandler המוגדר לעבוד עם האובייקט arnold :
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
כעת, אם אנו קוראים לשיטה כלשהי של ממשק ה-Person באובייקט ה-proxy, המטפל שלנו מיירט את הקריאה ומבצע את שיטת invoke() משלו במקום זאת. בואו ננסה להפעיל את שיטת main() ! פלט מסוף:

Hi!
מְעוּלֶה! אנו רואים שבמקום שיטת Person.introduce() המקורית , שיטת invoke() של PersonInvocationHandler() שלנו נקראת:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hi!");
   return null;
}
"היי!" מוצג בקונסולה, אבל זו לא בדיוק ההתנהגות שרצינו :/ מה שניסינו להשיג זה להציג תחילה "היי!" ולאחר מכן קרא לשיטה המקורית עצמה. במילים אחרות, הקריאה של השיטה
proxyArnold.introduce(arnold.getName());
צריך להציג "היי! שמי ארנולד", לא פשוט "היי!" איך נוכל להשיג זאת? זה לא מסובך: אנחנו רק צריכים לקחת כמה חירויות עם המטפל שלנו ועם שיטת invoke() :) שימו לב אילו טיעונים מועברים לשיטה הזו:
public Object invoke(Object proxy, Method method, Object[] args)
למתודה invoke () יש גישה למתודה שהופעלה במקור, ולכל הארגומנטים שלה (שיטת מתודה, Object[] args). במילים אחרות, אם נקרא למתודה proxyArnold.introduce(arnold.getName()) כך שמתודה invoke() תיקרא במקום שיטת introduce() אז בתוך השיטה הזו יש לנו גישה לשיטת introduce () המקורית והטיעון שלו! כתוצאה מכך, אנו יכולים לעשות זאת:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PersonInvocationHandler implements InvocationHandler {

   private Person person;

   public PersonInvocationHandler(Person person) {

       this.person = person;
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       System.out.println("Hi!");
       return method.invoke(person, args);
   }
}
כעת בשיטת invoke() הוספנו קריאה למתודה המקורית. אם ננסה כעת להפעיל את הקוד מהדוגמה הקודמת שלנו:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       // Create the original object
       Man arnold = new Man("Arnold", 30, "Thal", "Austria");

       // Get the class loader from the original object
       ClassLoader arnoldClassLoader = arnold.getClass().getClassLoader();

       // Get all the interfaces that the original object implements
       Class[] interfaces = arnold.getClass().getInterfaces();

       // Create a proxy for our arnold object
       Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

       // Call one of our original object's methods on the proxy object
       proxyArnold.introduce(arnold.getName());
   }
}
אז נראה שעכשיו הכל עובד כמו שצריך :) פלט מסוף:

Hi! My name is Arnold
מתי אולי תצטרך את זה? למעשה, לעתים קרובות למדי. דפוס העיצוב "דינמי פרוקסי" נמצא בשימוש פעיל בטכנולוגיות פופולריות... אה, אגב, שכחתי לציין ש- Dynamic Proxy הוא דפוס עיצובי! מזל טוב, למדת עוד אחד! :) פרוקסי דינמיים - 3לדוגמה, הוא נמצא בשימוש פעיל בטכנולוגיות ובמסגרות פופולריות הקשורות לאבטחה. תאר לעצמך שיש לך 20 שיטות שאמורות להתבצע רק על ידי משתמשים שנכנסו לתוכנית שלך. באמצעות הטכניקות שלמדת, תוכל בקלות להוסיף ל-20 השיטות הללו בדיקה כדי לראות אם המשתמש הזין אישורים חוקיים מבלי לשכפל את קוד האימות בכל שיטה. או נניח שאתה רוצה ליצור יומן שבו כל פעולות המשתמש יתועדו. זה גם קל לעשות באמצעות פרוקסי. אפילו עכשיו, אתה יכול פשוט להוסיף קוד לדוגמה שלנו למעלה כך ששם השיטה יוצג כשאתה קורא ל- invoke() , וזה יפיק יומן סופר פשוט של התוכנית שלנו :) לסיכום, שימו לב למגבלה אחת חשובה. אובייקט פרוקסי עובד עם ממשקים, לא מחלקות. נוצר פרוקסי עבור ממשק. תסתכל על הקוד הזה:
// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
כאן אנו יוצרים פרוקסי במיוחד עבור ממשק האדם . אם ננסה ליצור פרוקסי למחלקה, כלומר לשנות את סוג ההפניה ולנסות להטיל למחלקה Man , זה לא יעבוד.
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

proxyArnold.introduce(arnold.getName());
חריגה בשרשור "ראשי" java.lang.ClassCastException: לא ניתן להעיף com.sun.proxy.$Proxy0 ל-Man קיום ממשק הוא דרישה מוחלטת. פרוקסי עובדים עם ממשקים. זה הכל להיום :) ובכן, עכשיו יהיה טוב לפתור כמה משימות! :) עד הפעם הבאה!
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION