שלום! היום נסתכל על נושא חשוב ומעניין למדי - יצירת כיתות פרוקסי דינמיות בג'אווה. זה לא פשוט מדי, אז בואו ננסה להבין את זה עם דוגמאות :) אז, השאלה הכי חשובה: מה הם פרוקסי דינמיים ולמה הם מיועדים? מחלקה פרוקסי היא מעין "מבנה-על" על המחלקה המקורית, המאפשרת לנו לשנות את התנהגותה במידת הצורך. מה זה אומר לשנות התנהגות ואיך זה עובד? בואו נסתכל על דוגמה פשוטה. נניח שיש לנו ממשק
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 מוכנה ולא יכול פשוט לקחת ולשכתב את הקוד שלה. עם זאת, עלינו לשנות את התנהגותו. לדוגמה, איננו יודעים איזו שיטה תיקרא על האובייקט שלנו, ולכן אנו רוצים שהאדם יגיד תחילה "שלום!" כאשר יתקשר לכל אחד מהם. (אף אחד לא אוהב מישהו שהוא לא מנומס). מה עלינו לעשות במצב כזה? נצטרך כמה דברים:
-
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()
מדפיסים "שלום!" לקונסולה שבתוך השיטה).
- האובייקט המקורי והפרוקסי שלו.
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
דפוס ! מזל טוב, למדת עוד אחד! :) אז הוא נמצא בשימוש פעיל בטכנולוגיות ובמסגרות פופולריות הקשורות לאבטחה. תאר לעצמך שיש לך 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 נוכחות של ממשק היא דרישה חובה. פרוקסי עובד ברמת הממשק. זה הכל להיום :) כחומר נוסף בנושא פרוקסי, אני יכול להמליץ לכם על סרטון מעולה וגם מאמר טוב . ובכן, עכשיו זה יהיה נחמד לפתור כמה בעיות! :) נתראה!
GO TO FULL VERSION