CodeGym /Java Blogu /Rastgele /Java'da Dinamik Proxy'ler
John Squirrels
Seviye
San Francisco

Java'da Dinamik Proxy'ler

grupta yayınlandı
MERHABA! Bugün oldukça önemli ve ilginç bir konuyu ele alacağız: Java'da dinamik proxy sınıflarının oluşturulması. Çok basit değil, bu yüzden örnekler kullanarak anlamaya çalışacağız :) O halde en önemli soru: dinamik proxy'ler nedir ve ne içindir? Proxy sınıfı, orijinal sınıfın üstüne eklenen ve gerekirse orijinal sınıfın davranışını değiştirmemize izin veren bir tür "eklenti" dir. "Davranışı değiştirmek" ne anlama gelir ve bu nasıl çalışır? Basit bir örnek düşünün. Bir Person arayüzümüz ve bu arayüzü uygulayan basit bir Man sınıfımız olduğunu varsayalım.

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 sınıfımızın 3 yöntemi vardır: Introduction, sayAge ve sayWhereFrom. Bu sınıfı hazır bir JAR kitaplığının parçası olarak aldığımızı ve kodunu yeniden yazamayacağımızı hayal edin. Ama davranışını da değiştirmemiz gerekiyor. Örneğin, nesnemizde hangi yöntemin çağrılabileceğini bilmiyoruz ama karşımızdaki kişinin "Merhaba!" (kimse kaba davranan birini sevmez) yöntemlerden herhangi biri çağrıldığında. Dinamik proxy'ler - 2Bu durumda ne yapmalıyız? Birkaç şeye ihtiyacımız olacak:
  1. Çağırma İşleyicisi

Bu nedir? InvocationHandler , nesnemize yapılan herhangi bir yöntem çağrısını kesmemize ve ihtiyacımız olan ek davranışı eklememize izin veren özel bir arabirimdir. Kendi önleyicimizi yaratmamız gerekiyor, yani bu arayüzü uygulayan bir sınıf yaratmamız gerekiyor. Bu oldukça basit:

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;
   }
}
Yalnızca bir arabirim yöntemi uygulamamız gerekiyor: invoke() . Ve bu arada, ihtiyacımız olanı yapıyor: nesnemize yapılan tüm yöntem çağrılarını kesiyor ve gerekli davranışı ekliyor (invoke () yönteminin içinde, konsola "Merhaba!" çıktısı veriyoruz).
  1. Orijinal nesne ve proxy'leri.
Orijinal Man nesnemizi ve bunun için bir "eklenti" (proxy) yaratıyoruz :

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());

   }
}
Bu çok basit görünmüyor! Her kod satırı için özel olarak bir yorum ekledim. Neler olduğuna daha yakından bakalım. İlk satırda, proxy oluşturacağımız orijinal nesneyi basitçe yapıyoruz. Aşağıdaki iki satır size zorluk çıkarabilir:

 // 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();
Aslında burada gerçekten özel bir şey olmuyor :) Dördüncü satırda, özel Proxy sınıfını ve onun statik newProxyInstance() yöntemini kullanıyoruz:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Bu yöntem sadece proxy nesnemizi oluşturur. Son adımda aldığımız orijinal sınıf hakkındaki bilgileri ( ClassLoader ve arayüzlerinin bir listesi) ve ayrıca önceden oluşturulmuş InvocationHandler nesnesini yönteme iletiyoruz . Asıl önemli olan, orijinal arnold nesnemizi çağırma işleyicisine iletmeyi unutmamaktır , aksi takdirde "işlenecek" hiçbir şey olmayacaktır :) Sonunda ne elde ettik? Artık bir proxy nesnemiz var: proxyArnold . Kişi arayüzünün herhangi bir yöntemini çağırabilir . Neden? Çünkü ona burada tüm arayüzlerin bir listesini verdik:

// 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));
Artık Kişi arayüzünün tüm yöntemlerini biliyor . Ek olarak, proxy'mize Arnold nesnesiyle çalışacak şekilde yapılandırılmış bir PersonInvocationHandler nesnesi ilettik :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Şimdi, kişi arabiriminin herhangi bir yöntemini proxy nesnesinde çağırırsak , işleyicimiz aramayı keser ve bunun yerine kendi invoke() yöntemini yürütür. main() yöntemini çalıştırmayı deneyelim ! Konsol çıktısı:

Hi!
Harika! Orijinal Person.introduce() yöntemi yerine , PersonInvocationHandler() uygulamamızın invoke() yönteminin çağrıldığını görüyoruz:

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

   System.out.println("Hi!");
   return null;
}
"MERHABA!" konsolda görüntüleniyor, ancak bu tam olarak istediğimiz davranış değil :/ Elde etmeye çalıştığımız şey, önce "Merhaba!" ve sonra orijinal yöntemin kendisini çağırın.Başka bir deyişle, yöntem çağrısı

proxyArnold.introduce(arnold.getName());
sadece "Merhaba!" değil, "Merhaba! Benim adım Arnold" ifadesini göstermelidir. Bunu nasıl başarabiliriz? Bu karmaşık değil: işleyicimiz ve invoke() yöntemiyle biraz özgür olmamız gerekiyor :) Bu yönteme hangi argümanların iletildiğine dikkat edin:

public Object invoke(Object proxy, Method method, Object[] args)
invoke () yönteminin, başlangıçta çağrılan yönteme ve tüm bağımsız değişkenlerine (Metod yöntemi, Object[] bağımsız değişkenler) erişimi vardır. Başka bir deyişle, proxyArnold.introduce(arnold.getName()) yöntemini çağırırsak, böylece invoke() yönteminin Introduce() yerine invoke() yöntemi çağrılır , o zaman bu yöntemin içinde orijinal allow() yöntemine erişimimiz olur. ve argümanı! Sonuç olarak şunu yapabiliriz:

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);
   }
}
Şimdi invoke() yönteminde orijinal yönteme bir çağrı ekledik. Şimdi önceki örneğimizdeki kodu çalıştırmayı denersek:

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());
   }
}
o zaman artık her şeyin olması gerektiği gibi çalıştığını göreceğiz :) Konsol çıktısı:

Hi! My name is Arnold
Buna ne zaman ihtiyacınız olabilir? Aslında, oldukça sık. "Dinamik proxy" tasarım deseni, popüler teknolojilerde aktif olarak kullanılmaktadır... Ah, bu arada, Dinamik Proxy'nin bir tasarım deseni olduğunu söylemeyi unutmuşum! Tebrikler, bir tane daha öğrendiniz! :) Dinamik proxy'ler - 3Örneğin güvenlikle ilgili popüler teknolojilerde ve çerçevelerde aktif olarak kullanılmaktadır. Yalnızca programınızda oturum açmış kullanıcılar tarafından yürütülmesi gereken 20 yönteminiz olduğunu hayal edin. Öğrendiğiniz teknikleri kullanarak, bu 20 yönteme, her yöntemde doğrulama kodunu çoğaltmadan, kullanıcının geçerli kimlik bilgileri girip girmediğini görmek için kolayca bir kontrol ekleyebilirsiniz. Veya tüm kullanıcı eylemlerinin kaydedileceği bir günlük oluşturmak istediğinizi varsayalım. Bunu bir proxy kullanarak yapmak da kolaydır. Şimdi bile, invoke() öğesini çağırdığınızda yöntem adının görüntülenmesi için yukarıdaki örneğimize kod ekleyebilirsiniz ve bu, programımızın süper basit bir günlüğünü oluşturur :) Sonuç olarak, önemli bir sınırlamaya dikkat edin. Bir proxy nesnesi, sınıflarla değil arabirimlerle çalışır. Arayüz için bir proxy oluşturulur. Bu koda bir göz atın:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Burada, Kişi arayüzü için özel olarak bir proxy oluşturuyoruz . Sınıf için bir proxy oluşturmaya çalışırsak, yani referans türünü değiştirir ve Man sınıfına dönüştürmeye çalışırsak, çalışmaz.

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

proxyArnold.introduce(arnold.getName());
"main" dizisindeki istisna java.lang.ClassCastException: com.sun.proxy.$Proxy0, Man'e aktarılamaz Bir arayüze sahip olmak mutlak bir gerekliliktir. Proxy'ler arayüzlerle çalışır. Bugünlük bu kadar :) Pekala, şimdi birkaç görevi çözmek iyi olur! :) Bir sonrakine kadar!
Yorumlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION