हाय! आज आपण एका महत्त्वाच्या आणि मनोरंजक विषयावर विचार करू: जावामध्ये डायनॅमिक प्रॉक्सी वर्गांची निर्मिती. हे खूप सोपे नाही, म्हणून आम्ही उदाहरणे वापरून ते शोधण्याचा प्रयत्न करू :) तर, सर्वात महत्वाचा प्रश्न: डायनॅमिक प्रॉक्सी काय आहेत आणि ते कशासाठी आहेत? प्रॉक्सी वर्ग हा मूळ वर्गाच्या वरचा एक प्रकारचा "अॅड-ऑन" आहे, जो आवश्यक असल्यास मूळ वर्गाचे वर्तन बदलू देतो. "वर्तन बदलणे" म्हणजे काय आणि ते कसे कार्य करते? एक साधे उदाहरण विचारात घ्या. समजा आपल्याकडे एक व्यक्ती इंटरफेस आहे आणि हा इंटरफेस लागू करणारा एक साधा मॅन क्लास आहे

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.
}
आमच्या मॅन क्लासमध्ये 3 पद्धती आहेत: परिचय करा, वय सांगा आणि व्हेअरफ्रॉम सांगा. कल्पना करा की आम्हाला हा वर्ग ऑफ-द-शेल्फ JAR लायब्ररीचा भाग म्हणून मिळाला आहे आणि आम्ही त्याचा कोड पुन्हा लिहू शकत नाही. पण त्याची वागणूकही बदलायला हवी. उदाहरणार्थ, आमच्या ऑब्जेक्टवर कोणती पद्धत कॉल केली जाऊ शकते हे आम्हाला माहित नाही, परंतु आम्हाला आमच्या व्यक्तीने "हाय!" म्हणायचे आहे. (कोणालाही असभ्य व्यक्ती आवडत नाही) जेव्हा कोणत्याही पद्धतींना बोलावले जाते. डायनॅमिक प्रॉक्सी - 2या परिस्थितीत आपण काय करावे? आम्हाला काही गोष्टींची आवश्यकता असेल:
  1. आवाहन हँडलर

हे काय आहे? 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() . आणि, तसे, ते आम्हाला आवश्यक तेच करते: ते आमच्या ऑब्जेक्टवर सर्व मेथड कॉल्स इंटरसेप्ट करते आणि आवश्यक वर्तन जोडते (इनव्होक () पद्धतीमध्ये, आम्ही कन्सोलवर "हाय!" आउटपुट करतो).
  1. मूळ ऑब्जेक्ट आणि त्याची प्रॉक्सी.
आम्ही आमचे मूळ मॅन ऑब्जेक्ट आणि त्यासाठी "अॅड-ऑन" (प्रॉक्सी) तयार करतो:

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();
खरं तर, इथे काही विशेष घडत नाहीये :) चौथ्या ओळीत, आम्ही विशेष प्रॉक्सी वर्ग आणि त्याची स्थिर newProxyInstance() पद्धत वापरतो:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
ही पद्धत फक्त आमचे प्रॉक्सी ऑब्जेक्ट तयार करते. आम्ही मूळ वर्गाविषयीची माहिती या पद्धतीकडे पाठवतो, जी आम्हाला शेवटच्या टप्प्यात प्राप्त झाली होती (त्याचा ClassLoader आणि त्याच्या इंटरफेसची सूची), तसेच पूर्वी तयार केलेला InvocationHandler ऑब्जेक्ट. मुख्य म्हणजे आमची मूळ अर्नॉल्ड ऑब्जेक्ट इनव्होकेशन हँडलरकडे देण्यास विसरू नका , अन्यथा "हँडल" करण्यासारखे काहीही राहणार नाही :) आम्ही काय केले? आमच्याकडे आता एक प्रॉक्सी ऑब्जेक्ट आहे: 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));
आता त्याला व्यक्ती इंटरफेसच्या सर्व पद्धती माहित आहेत . याव्यतिरिक्त, आम्ही आमच्या प्रॉक्सीला अर्नोल्ड ऑब्जेक्टसह कार्य करण्यासाठी कॉन्फिगर केलेला PersonInvocationHandler ऑब्जेक्ट पाठवला आहे :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
आता आपण प्रॉक्सी ऑब्जेक्टवर व्यक्ती इंटरफेसच्या कोणत्याही पद्धतीला कॉल केल्यास , आमचा हँडलर कॉल इंटरसेप्ट करतो आणि त्याऐवजी स्वतःची इनव्होक() पद्धत कार्यान्वित करतो. चला main() पद्धत चालवण्याचा प्रयत्न करूया ! कन्सोल आउटपुट:

Hi!
उत्कृष्ट! आम्ही पाहतो की मूळ Person.introduce() पद्धतीऐवजी, आमच्या PersonInvocationHandler() च्या invoke() पद्धतीला म्हणतात:

@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 () मेथडला मूळ पद्धतीने मागवलेल्या पद्धतीचा आणि त्याच्या सर्व वितर्कांचा (पद्धत पद्धत, ऑब्जेक्ट[] आर्ग्स) प्रवेश असतो. दुसर्‍या शब्दांत, जर आपण proxyArnold.introduce(arnold.getName()) पद्धत म्हणतो म्हणजे invoke() पद्धत ओळखली जाते () पद्धतीऐवजी, तर या पद्धतीमध्ये आपल्याला मूळ परिचय() पद्धतीचा प्रवेश असतो. आणि त्याचा युक्तिवाद! परिणामी, आम्ही हे करू शकतो:

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
तुम्हाला याची कधी गरज पडू शकते? खरं तर, बरेचदा. "डायनॅमिक प्रॉक्सी" डिझाइन पॅटर्न लोकप्रिय तंत्रज्ञानामध्ये सक्रियपणे वापरला जातो... अरेरे, मी हे सांगायला विसरलो की डायनॅमिक प्रॉक्सी एक डिझाइन पॅटर्न आहे! अभिनंदन, तुम्ही आणखी एक शिकलात! :) डायनॅमिक प्रॉक्सी - 3उदाहरणार्थ, हे सुरक्षिततेशी संबंधित लोकप्रिय तंत्रज्ञान आणि फ्रेमवर्कमध्ये सक्रियपणे वापरले जाते. कल्पना करा की तुमच्याकडे 20 पद्धती आहेत ज्या फक्त तुमच्या प्रोग्राममध्ये साइन इन केलेल्या वापरकर्त्यांद्वारेच अंमलात आणल्या पाहिजेत. तुम्ही शिकलेल्या तंत्रांचा वापर करून, तुम्ही या 20 पद्धतींमध्ये सहजपणे जोडू शकता की वापरकर्त्याने प्रत्येक पद्धतीमध्ये पडताळणी कोड डुप्लिकेट न करता वैध क्रेडेन्शियल्स एंटर केले आहेत की नाही हे पाहण्यासाठी. किंवा समजा तुम्हाला एक लॉग तयार करायचा आहे जिथे वापरकर्त्याच्या सर्व क्रिया रेकॉर्ड केल्या जातील. प्रॉक्सी वापरून हे करणे देखील सोपे आहे. आताही, तुम्ही आमच्या वरील उदाहरणात फक्त कोड जोडू शकता जेणेकरुन तुम्ही invoke() ला कॉल करता तेव्हा पद्धतीचे नाव प्रदर्शित होईल आणि आमच्या प्रोग्रामचा एक अतिशय सोपा लॉग तयार करेल :) शेवटी, एका महत्त्वाच्या मर्यादेकडे लक्ष द्या. प्रॉक्सी ऑब्जेक्ट इंटरफेससह कार्य करते, वर्ग नाही. इंटरफेससाठी प्रॉक्सी तयार केली जाते. हा कोड पहा:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
येथे आम्ही विशेषत: व्यक्ती इंटरफेससाठी प्रॉक्सी तयार करतो . जर आपण वर्गासाठी प्रॉक्सी तयार करण्याचा प्रयत्न केला, म्हणजे संदर्भाचा प्रकार बदलून मॅन क्लासमध्ये टाकण्याचा प्रयत्न केला, तर ते कार्य करणार नाही.

Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));

proxyArnold.introduce(arnold.getName());
"मुख्य" थ्रेडमधील अपवाद java.lang.ClassCastException: com.sun.proxy.$Proxy0 मनुष्याला कास्ट करता येत नाही इंटरफेस असणे ही पूर्ण आवश्यकता आहे. प्रॉक्सी इंटरफेससह कार्य करतात. आजसाठी एवढंच :) बरं, आता काही कार्ये सोडवणे चांगले होईल! :) पुढच्या वेळे पर्यंत!