CodeGym /Blogue Java /Random-PT /Proxies Dinâmicos em Java
John Squirrels
Nível 41
San Francisco

Proxies Dinâmicos em Java

Publicado no grupo Random-PT
Oi! Hoje vamos considerar um tópico bastante importante e interessante: a criação de classes proxy dinâmicas em Java. Não é muito simples, então vamos tentar descobrir usando exemplos :) Então, a pergunta mais importante: o que são proxies dinâmicos e para que servem? Uma classe proxy é uma espécie de "add-on" sobre a classe original, que nos permite alterar o comportamento da classe original, se necessário. O que significa "mudar comportamento" e como isso funciona? Considere um exemplo simples. Suponha que temos uma interface Person e uma classe Man simples que implementa essa interface

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.
}
Nossa classe Man tem 3 métodos: introdutor, sayAge e sayWhereFrom. Imagine que obtivemos essa classe como parte de uma biblioteca JAR pronta para uso e não podemos simplesmente reescrever seu código. Mas também precisamos mudar seu comportamento. Por exemplo, não sabemos qual método pode ser chamado em nosso objeto, mas queremos que nossa pessoa diga "Oi!" (ninguém gosta de alguém que é indelicado) quando qualquer um dos métodos é chamado. Proxies dinâmicos - 2O que devemos fazer nesta situação? Vamos precisar de algumas coisas:
  1. Manipulador de Invocação

O que é isso? InvocationHandler é uma interface especial que nos permite interceptar qualquer chamada de método para nosso objeto e adicionar o comportamento adicional de que precisamos. Precisamos criar nosso próprio interceptor, ou seja, criar uma classe que implemente essa interface. Isso é bem simples:

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;
   }
}
Precisamos implementar apenas um método de interface: invoke() . E, a propósito, ele faz o que precisamos: ele intercepta todas as chamadas de método para nosso objeto e adiciona o comportamento necessário (dentro do método invoke() , enviamos "Hi!" para o console).
  1. O objeto original e seus proxies.
Criamos nosso objeto Man original e um "complemento" (proxy) para ele:

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

   }
}
Isso não parece muito simples! Eu adicionei especificamente um comentário para cada linha de código. Vamos dar uma olhada no que está acontecendo. Na primeira linha, simplesmente criamos o objeto original para o qual criaremos proxies. As duas linhas a seguir podem causar dificuldades:

 // 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();
Na verdade, não há nada realmente especial acontecendo aqui :) Na quarta linha, usamos a classe especial Proxy e seu método estático newProxyInstance() :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Este método apenas cria nosso objeto proxy. Passamos para o método as informações sobre a classe original, que recebemos na última etapa (seu ClassLoader e uma lista de suas interfaces), bem como o objeto InvocationHandler criado anteriormente. O principal é não esquecer de passar nosso objeto arnold original para o manipulador de invocação, caso contrário, não haverá nada para "manipular" :) Com o que terminamos? Agora temos um objeto proxy: proxyArnold . Ele pode chamar qualquer método da interface Person . Por que? Porque nós demos uma lista de todas as interfaces aqui:

// 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));
Agora ele conhece todos os métodos da interface Person . Além disso, passamos para nosso proxy um objeto PersonInvocationHandler configurado para trabalhar com o objeto arnold :

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Agora, se chamarmos qualquer método da interface Person no objeto proxy, nosso manipulador interceptará a chamada e executará seu próprio método invoke() . Vamos tentar executar o método main() ! Saída do console:

Hi!
Excelente! Vemos que, em vez do método original Person.introduce() , o método invoke() de nosso PersonInvocationHandler() é chamado:

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

   System.out.println("Hi!");
   return null;
}
"Oi!" é exibido no console, mas esse não é exatamente o comportamento que queríamos :/ O que estávamos tentando alcançar é exibir primeiro "Hi!" e, em seguida, chamar o próprio método original. Em outras palavras, a chamada do método

proxyArnold.introduce(arnold.getName());
deve exibir "Oi! Meu nome é Arnold", não simplesmente "Oi!" Como podemos conseguir isso? Não é complicado: só precisamos tomar algumas liberdades com nosso manipulador e o método invoke() :) Preste atenção em quais argumentos são passados ​​para este método:

public Object invoke(Object proxy, Method method, Object[] args)
O método invoke() tem acesso ao método invocado originalmente e a todos os seus argumentos (método Method, Object[] args). Em outras palavras, se chamarmos o método proxyArnold.introduce(arnold.getName()) para que o método invoke() seja chamado em vez do método introdutor() , então, dentro desse método, temos acesso ao método introdutor() original e seu argumento! Como resultado, podemos fazer isso:

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);
   }
}
Agora, no método invoke() adicionamos uma chamada ao método original. Se agora tentarmos executar o código do nosso exemplo anterior:

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());
   }
}
então veremos que agora tudo funciona como deveria :) Saída do console:

Hi! My name is Arnold
Quando você pode precisar disso? Na verdade, com bastante frequência. O padrão de design "proxy dinâmico" é usado ativamente em tecnologias populares... Ah, a propósito, esqueci de mencionar que o Dynamic Proxy é um padrão de design! Parabéns, você aprendeu mais uma! :) Proxies dinâmicos - 3Por exemplo, é usado ativamente em tecnologias e estruturas populares relacionadas à segurança. Imagine que você tenha 20 métodos que devem ser executados apenas por usuários que estão conectados ao seu programa. Usando as técnicas que você aprendeu, você pode facilmente adicionar a esses 20 métodos uma verificação para ver se o usuário inseriu credenciais válidas sem duplicar o código de verificação em cada método. Ou suponha que você queira criar um log onde todas as ações do usuário serão registradas. Isso também é fácil de fazer usando um proxy. Mesmo agora, você pode simplesmente adicionar código ao nosso exemplo acima para que o nome do método seja exibido quando você chamar invoke() , e isso produziria um log super simples do nosso programa :) Em conclusão, preste atenção a uma limitação importante. Um objeto proxy funciona com interfaces, não com classes. Um proxy é criado para uma interface. Dê uma olhada neste código:

// Create a proxy for our arnold object
Person proxyArnold = (Person) Proxy.newProxyInstance(arnoldClassLoader, interfaces, new PersonInvocationHandler(arnold));
Aqui criamos um proxy especificamente para a interface Person . Se tentarmos criar um proxy para a classe, ou seja, alterar o tipo de referência e tentar converter para a classe Man , não funcionará.

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

proxyArnold.introduce(arnold.getName());
Exceção no encadeamento "principal" java.lang.ClassCastException: com.sun.proxy.$Proxy0 não pode ser convertido para Man Ter uma interface é um requisito absoluto. Proxies trabalham com interfaces. Por hoje é tudo :) Bem, agora seria bom resolver algumas tarefas! :) Até a próxima vez!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION