JavaRush /Blog Java /Random-ES /Proxies dinámicos en Java

Proxies dinámicos en Java

Publicado en el grupo Random-ES
¡Hola! Hoy veremos un tema bastante importante e interesante: la creación de clases de proxy dinámicas en Java. No es demasiado simple, así que intentemos resolverlo con ejemplos :) Entonces, la pregunta más importante: ¿qué son los proxies dinámicos y para qué sirven? Una clase proxy es una especie de “superestructura” sobre la clase original, que nos permite cambiar su comportamiento si es necesario. ¿Qué significa cambiar el comportamiento y cómo funciona? Veamos un ejemplo sencillo. Digamos que tenemos una interfaz Persony una clase simple Manque implementa esta interfaz.
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 + " años");
   }

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

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

   //..геттеры, сеттеры, и т.д.
}
Nuestra clase Mantiene 3 métodos: preséntate, di tu edad y di de dónde eres. Imaginemos que recibimos esta clase como parte de una biblioteca JAR ya preparada y no podemos simplemente tomar y reescribir su código. Sin embargo, necesitamos cambiar su comportamiento. Por ejemplo, no sabemos qué método se llamará en nuestro objeto y, por lo tanto, queremos que la persona diga primero "¡Hola!" cuando llame a cualquiera de ellos. (a nadie le gusta alguien que es descortés). Proxies dinámicos - 1¿Qué debemos hacer en tal situación? Necesitaremos algunas cosas:
  1. InvocationHandler

¿Lo que es? Puede traducirse literalmente como "interceptor de llamadas". Esto describe su propósito con bastante precisión. InvocationHandleres una interfaz especial que nos permite interceptar cualquier llamada a método de nuestro objeto y agregar el comportamiento adicional que necesitamos. Necesitamos crear nuestro propio interceptor, es decir, crear una clase e implementar esta interfaz. Es bastante simple:
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("¡Hola!");
       return null;
   }
}
Necesitamos implementar solo un método de interfaz: invoke(). De hecho, hace lo que necesitamos: intercepta todas las llamadas a métodos de nuestro objeto y agrega el comportamiento necesario (aquí imprimimos invoke()"¡Hola!" en la consola dentro del método).
  1. El objeto original y su proxy.
Creemos un objeto original Many una "superestructura" (proxy) para él:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

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

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

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

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

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

   }
}
¡No parece muy simple! Escribí específicamente un comentario para cada línea de código: echemos un vistazo más de cerca a lo que sucede allí.

En la primera línea simplemente creamos el objeto original para el cual crearemos un proxy. Las siguientes dos líneas pueden causarle confusión:
//Получаем загрузчик класса у оригинального un objetoа
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

//Получаем все интерфейсы, которые реализует оригинальный un objeto
Class[] interfaces = vasia.getClass().getInterfaces();
Pero realmente no está sucediendo nada especial aquí :) Para crear un proxy, necesitamos ClassLoaderel (cargador de clases) del objeto original y una lista de todas las interfaces que nuestra clase original (es decir, Man) implementa. Si no sabes qué es ClassLoader, puedes leer este artículo sobre cómo cargar clases en la JVM o este en Habré , pero no te molestes demasiado todavía. Solo recuerde que obtenemos un poco de información adicional que luego necesitaremos para crear el objeto proxy. En la cuarta línea utilizamos una clase especial Proxyy su método estático newProxyInstance():
//Создаем прокси нашего un objetoа vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Este método simplemente crea nuestro objeto proxy. Al método le pasamos la información sobre la clase original que recibimos en el paso anterior (ella ClassLoadery la lista de sus interfaces), así como el objeto del interceptor que creamos anteriormente: InvocationHandler'a. Lo principal es no olvidar pasar nuestro objeto original al interceptor vasia, de lo contrario no tendrá nada que “interceptar” :) ¿Con qué terminamos? Ahora tenemos un objeto proxy vasiaProxy. Puede llamar a cualquier método de interfazPerson . ¿Por qué? Porque le pasamos una lista de todas las interfaces, aquí:
//Получаем все интерфейсы, которые реализует оригинальный un objeto
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего un objetoа vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Ahora está "consciente" de todos los métodos de interfaz Person. Además, le pasamos a nuestro proxy un objeto PersonInvocationHandlerconfigurado para trabajar con el objeto vasia:
//Создаем прокси нашего un objetoа vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Ahora, si llamamos a cualquier método de interfaz en el objeto proxy Person, nuestro interceptor "captará" esta llamada y ejecutará su propio método invoke(). ¡Intentemos ejecutar el método main()! Salida de consola: ¡Hola! ¡Excelente! Vemos que en lugar del método real, nuestro Person.introduce()método se llama : invoke()PersonInvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("¡Hola!");
   return null;
}
Y la consola mostró "¡Hola!" Pero este no es exactamente el comportamiento que queríamos obtener :/ Según nuestra idea, primero debería mostrarse "¡Hola!" y luego debería funcionar el método al que estamos llamando. En otras palabras, este método llama:
proxyVasia.introduce(vasia.getName());
debería enviar a la consola “¡Hola! Mi nombre es Vasya”, y no sólo “¡Hola!” ¿Cómo podemos lograr esto? Nada complicado: sólo tienes que jugar un poco con nuestro interceptor y método invoke():) Presta atención a los argumentos que se pasan a este método:
public Object invoke(Object proxy, Method method, Object[] args)
Un método invoke()tiene acceso al método al que se llama y a todos sus argumentos (método método, argumentos Objeto[]). En otras palabras, si llamamos a un método proxyVasia.introduce(vasia.getName()), y en lugar de un método , introduce()se llama a un método invoke(), ¡dentro de ese método tenemos acceso tanto al método original introduce()como a su argumento! Como resultado, podemos hacer algo como esto:
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("¡Hola!");
       return method.invoke(person, args);
   }
}
Ahora hemos agregado invoke()una llamada al método original al método. Si ahora intentamos ejecutar el código de nuestro ejemplo anterior:
import java.lang.reflect.Proxy;

public class Main {

   public static void main(String[] args) {

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

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

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

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

       //Вызываем у прокси un objetoа один из методов нашего оригинального un objetoа
       proxyVasia.introduce(vasia.getName());
   }
}
luego veremos que ahora todo funciona como debería :) Salida de consola: ¡Hola! Mi nombre es Vasya ¿ Dónde podrías necesitarlo? De hecho, en muchos lugares. El patrón de diseño “proxy dinámico” se utiliza activamente en tecnologías populares... y por cierto, ¡olvidé decirte que Dynamic Proxyes un patrón ! ¡Felicitaciones, has aprendido otro! :) Proxies dinámicos - 2Por lo tanto, se utiliza activamente en tecnologías y marcos populares relacionados con la seguridad. Imagine que tiene 20 métodos que solo pueden ser ejecutados por usuarios que hayan iniciado sesión en su programa. Utilizando las técnicas que ha aprendido, puede agregar fácilmente a estos 20 métodos una verificación para ver si el usuario ingresó un nombre de usuario y contraseña, sin duplicar el código de verificación por separado en cada método. O, por ejemplo, si desea crear un registro donde se registrarán todas las acciones del usuario, también es fácil hacerlo utilizando un proxy. Incluso ahora puedes: simplemente agrega código al ejemplo para que el nombre del método se muestre en la consola cuando se llame invoke()y obtendrás un registro simple de nuestro programa :) Al final de la conferencia, presta atención a una cosa importante limitación . La creación de un objeto proxy se produce en el nivel de interfaz, no en el nivel de clase. Se crea un proxy para la interfaz. Eche un vistazo a este código:
//Создаем прокси нашего un objetoа vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Aquí creamos un proxy específicamente para la interfaz Person. Si intentamos crear un proxy para la clase, es decir, cambiamos el tipo de enlace e intentamos transmitir a la clase Man, nada funcionará.
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

proxyVasia.introduce(vasia.getName());
Excepción en el hilo "principal" java.lang.ClassCastException: com.sun.proxy.$Proxy0 no se puede convertir a Man La presencia de una interfaz es un requisito obligatorio. El proxy funciona a nivel de interfaz. Eso es todo por hoy :) Como material adicional sobre el tema de los proxies, puedo recomendaros un excelente vídeo y también un buen artículo . Bueno, ¡ahora sería bueno resolver algunos problemas! :) ¡Nos vemos!
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION