JavaRush /בלוג Java /Random-HE /פרוקסי דינמיים ב-Java

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

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

   public void introduce(String name);

   public void sayAge(int age);

   public void sayFrom(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("Меня зовут " + this.name);
   }

   @Override
   public void sayAge(int age) {
       System.out.println("Мне " + this.age + " years");
   }

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

       System.out.println("Я из города " + this.city + ", " + this.country);
   }

   //..геттеры, сеттеры, и т.д.
}
לכיתה שלנו Manיש 3 שיטות: הציגו את עצמכם, אמרו את גילכם ותגידו מאיפה אתם. בואו נדמיין שקיבלנו את המחלקה הזו כחלק מספריית JAR מוכנה ולא יכול פשוט לקחת ולשכתב את הקוד שלה. עם זאת, עלינו לשנות את התנהגותו. לדוגמה, איננו יודעים איזו שיטה תיקרא על האובייקט שלנו, ולכן אנו רוצים שהאדם יגיד תחילה "שלום!" כאשר יתקשר לכל אחד מהם. (אף אחד לא אוהב מישהו שהוא לא מנומס). פרוקסי דינמיים - 1מה עלינו לעשות במצב כזה? נצטרך כמה דברים:
  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("Hello!");
       return null;
   }
}
עלינו ליישם רק שיטת ממשק אחת - invoke(). זה, למעשה, עושה את מה שאנחנו צריכים - הוא מיירט את כל קריאות השיטה לאובייקט שלנו ומוסיף את ההתנהגות הדרושה (כאן אנו invoke()מדפיסים "שלום!" לקונסולה שבתוך השיטה).
  1. האובייקט המקורי והפרוקסי שלו.
בואו ניצור אובייקט מקורי Manו"מבנה על" (פרוקסי) עבורו:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());

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

בשורה הראשונה אנחנו פשוט יוצרים את האובייקט המקורי שעבורו ניצור פרוקסי. שתי השורות הבאות עלולות לגרום לך לבלבול:
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
אבל באמת לא קורה כאן שום דבר מיוחד :) כדי ליצור פרוקסי, אנחנו צריכים ClassLoaderאת (מטעין המחלקה) של האובייקט המקורי ורשימת כל הממשקים שהמחלקה המקורית שלנו (כלומר Man) מיישמת. אם אתה לא יודע מה זה ClassLoader, אתה יכול לקרוא את המאמר הזה על טעינת שיעורים ל-JVM או זה ב-Habré , אבל אל תתעסק בזה יותר מדי עדיין. רק זכור שאנו מקבלים מעט מידע נוסף שנצטרך לאחר מכן כדי ליצור את אובייקט ה-proxy. בשורה הרביעית אנו משתמשים במחלקה מיוחדת Proxyובשיטה הסטטית שלה newProxyInstance():
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
שיטה זו רק יוצרת את אובייקט ה-proxy שלנו. לשיטה אנו מעבירים את המידע על המחלקה המקורית שקיבלנו בשלב הקודם (אותה ClassLoaderורשימת הממשקים שלה), וכן את אובייקט המיירט שיצרנו קודם לכן – InvocationHandler'א. העיקר אל תשכח להעביר את האובייקט המקורי שלנו למיירט vasia, אחרת לא יהיה לו מה "ליירט" :) עם מה הגענו? כעת יש לנו אובייקט פרוקסי vasiaProxy. זה יכול לקרוא לכל שיטות ממשקPerson . למה? מכיוון שהעברנו לה רשימה של כל הממשקים - כאן:
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
כעת הוא "מודע" לכל שיטות הממשק Person. בנוסף, העברנו ל-proxy שלנו אובייקט PersonInvocationHandlerשהוגדר לעבוד עם האובייקט vasia:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
כעת, אם אנו קוראים לשיטת ממשק כלשהי באובייקט ה-proxy Person, המיירט שלנו "יתפוס" את הקריאה הזו ויבצע שיטה משלו במקום זאת invoke(). בואו ננסה להפעיל את השיטה main()! פלט מסוף: שלום! גדול! אנו רואים שבמקום השיטה האמיתית, השיטה שלנו Person.introduce()נקראת : invoke()PersonInvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hello!");
   return null;
}
והקונסולה הציגה "שלום!" אבל זו לא בדיוק ההתנהגות שרצינו לקבל :/ לפי הרעיון שלנו, יש להציג תחילה "הלו!", ואז השיטה עצמה שאנו קוראים לה צריכה לעבוד. במילים אחרות, השיטה הזו קוראת:
proxyVasia.introduce(vasia.getName());
צריך להוציא לקונסולה "Hello! שמי ואסיה, ולא רק "שלום!" איך נוכל להשיג זאת? שום דבר מסובך: צריך רק להתעסק קצת עם המיירט והשיטה שלנו invoke():) שימו לב איזה טיעונים מועברים לשיטה הזו:
public Object invoke(Object proxy, Method method, Object[] args)
למתודה invoke()יש ​​גישה למתודה שהיא נקראת במקום ולכל הארגומנטים שלה (שיטת מתודה, Object[] args). במילים אחרות, אם אנו קוראים למתודה proxyVasia.introduce(vasia.getName()), ובמקום שיטה , introduce()שיטה נקראת invoke(), בתוך השיטה הזו יש לנו גישה גם לשיטה המקורית 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("Hello!");
       return method.invoke(person, args);
   }
}
כעת הוספנו invoke()קריאה לשיטה המקורית לשיטה. אם ננסה כעת להפעיל את הקוד מהדוגמה הקודמת שלנו:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

       //Создаем оригинальный an object
       Man vasia = new Man("Vasya", 30, "Санкт-Петербург", "Россия");

       //Получаем загрузчик класса у оригинального an object
       ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

       //Получаем все интерфейсы, которые реализует оригинальный an object
       Class[] interfaces = vasia.getClass().getInterfaces();

       //Создаем прокси нашего an object vasia
       Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

       //Вызываем у прокси an object один из методов нашего оригинального an object
       proxyVasia.introduce(vasia.getName());
   }
}
אז נראה שעכשיו הכל עובד כמו שצריך :) פלט מסוף: שלום! שמי ואסיה. איפה תזדקק לזה? למעשה, הרבה מקומות. דפוס העיצוב של "פרוקסי דינמי" נמצא בשימוש פעיל בטכנולוגיות פופולריות... ודרך אגב, שכחתי להגיד לך שזה Dynamic Proxyדפוס ! מזל טוב, למדת עוד אחד! :) פרוקסי דינמיים - 2אז הוא נמצא בשימוש פעיל בטכנולוגיות ובמסגרות פופולריות הקשורות לאבטחה. תאר לעצמך שיש לך 20 שיטות שיכולות להתבצע רק על ידי משתמשים מחוברים לתוכנית שלך. באמצעות הטכניקות שלמדת, אתה יכול בקלות להוסיף ל-20 השיטות הללו בדיקה כדי לראות אם המשתמש הזין שם משתמש וסיסמה, מבלי לשכפל את קוד האימות בנפרד בכל שיטה. או, למשל, אם ברצונך ליצור יומן שבו כל פעולות המשתמש יתועדו, זה גם קל לעשות באמצעות פרוקסי. זה אפשרי גם עכשיו: פשוט הוסף קוד לדוגמא כדי ששם השיטה יוצג בקונסולה כשקוראים לו invoke(), ותקבל יומן פשוט של התוכנית שלנו :) בסוף ההרצאה, שימו לב לאחד חשוב הגבלה . יצירת אובייקט פרוקסי מתרחשת ברמת הממשק, לא ברמת המחלקה. נוצר פרוקסי עבור הממשק. תסתכל על הקוד הזה:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
כאן אנו יוצרים פרוקסי במיוחד עבור הממשק Person. אם ננסה ליצור פרוקסי עבור המחלקה, כלומר נשנה את סוג הקישור וננסה להעיף למחלקה Man, שום דבר לא יעבוד.
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

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