JavaRush /Blogue Java /Random-PT /Proxies Dinâmicos em Java

Proxies Dinâmicos em Java

Publicado no grupo Random-PT
Olá! Hoje veremos um tópico bastante importante e interessante - a criação de classes de proxy dinâmicas em Java. Não é muito simples, então vamos tentar descobrir com exemplos :) Então, a questão mais importante: o que são proxies dinâmicos e para que servem? Uma classe proxy é uma espécie de “superestrutura” sobre a classe original, que nos permite alterar seu comportamento se necessário. O que significa mudar o comportamento e como isso funciona? Vejamos um exemplo simples. Digamos que temos uma interface Persone uma classe simples Manque implementa esta interface
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);
   }

   //..геттеры, сеттеры, и т.д.
}
Nossa aula Manpossui 3 métodos: apresente-se, diga sua idade e diga de onde você é. Vamos imaginar que recebemos essa classe como parte de uma biblioteca JAR pronta e não podemos simplesmente pegar e reescrever seu código. No entanto, precisamos mudar seu comportamento. Por exemplo, não sabemos qual método será chamado em nosso objeto e, portanto, queremos que a pessoa diga primeiro “Olá!” ao chamar qualquer um deles. (ninguém gosta de alguém que é indelicado). Proxies dinâmicos - 1O que devemos fazer em tal situação? Precisaremos de algumas coisas:
  1. InvocationHandler

O que é isso? Pode ser traduzido literalmente como “interceptador de chamadas”. Isso descreve seu propósito com bastante precisão. InvocationHandleré uma interface especial que nos permite interceptar qualquer chamada de método para nosso objeto e adicionar o comportamento adicional necessário. Precisamos fazer nosso próprio interceptador - ou seja, criar uma classe e implementar essa interface. É 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("Hello!");
       return null;
   }
}
Precisamos implementar apenas um método de interface - invoke(). Na verdade, ele faz o que precisamos - intercepta todas as chamadas de método para nosso objeto e adiciona o comportamento necessário (aqui imprimimos invoke()“Hello!” no console dentro do método).
  1. O objeto original e seu proxy.
Vamos criar um objeto original Mane uma “superestrutura” (proxy) para ele:
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());

   }
}
Não parece muito simples! Escrevi especificamente um comentário para cada linha de código: vamos dar uma olhada mais de perto no que está acontecendo lá.

Na primeira linha simplesmente criamos o objeto original para o qual criaremos um proxy. As duas linhas a seguir podem causar confusão:
//Получаем загрузчик класса у оригинального an object
ClassLoader vasiaClassLoader = vasia.getClass().getClassLoader();

//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();
Mas não há realmente nada de especial acontecendo aqui :) Para criar um proxy, precisamos ClassLoaderdo (carregador de classe) do objeto original e de uma lista de todas as interfaces que nossa classe original (ou seja, Man) implementa. Se você não sabe o que é ClassLoader, você pode ler este artigo sobre como carregar classes na JVM ou este no Habré , mas não se preocupe muito com isso ainda. Apenas lembre-se de que estamos obtendo algumas informações extras que precisaremos para criar o objeto proxy. Na quarta linha usamos uma classe especial Proxye seu método estático newProxyInstance():
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Este método apenas cria nosso objeto proxy. Para o método passamos as informações sobre a classe original que recebemos no passo anterior (ela ClassLoadere a lista de suas interfaces), bem como o objeto do interceptor que criamos anteriormente - InvocationHandler'a. O principal é não se esquecer de passar nosso objeto original para o interceptor vasia, caso contrário não haverá nada para “interceptar” :) Com o que acabamos? Agora temos um objeto proxy vasiaProxy. Ele pode chamar qualquer método de interfacePerson . Por que? Porque passamos uma lista de todas as interfaces - aqui:
//Получаем все интерфейсы, которые реализует оригинальный an object
Class[] interfaces = vasia.getClass().getInterfaces();

//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Agora ele está "consciente" de todos os métodos de interface Person. Além disso, passamos para nosso proxy um objeto PersonInvocationHandlerconfigurado para funcionar com o objeto vasia:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Agora, se chamarmos qualquer método de interface no objeto proxy Person, nosso interceptador irá “capturar” essa chamada e executar seu próprio método invoke(). Vamos tentar executar o método main()! Saída do console: Olá! Ótimo! Vemos que em vez do método real, nosso Person.introduce()método é chamado : invoke()PersonInvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

   System.out.println("Hello!");
   return null;
}
E o console exibiu “Olá!” Mas este não é exatamente o comportamento que queríamos obter :/ De acordo com a nossa ideia, “Hello!” deveria ser exibido primeiro, e então o próprio método que estamos chamando deveria funcionar. Em outras palavras, este método chama:
proxyVasia.introduce(vasia.getName());
deve enviar para o console “Olá! Meu nome é Vasya” e não apenas “Olá!” Como podemos conseguir isso? Nada complicado: basta mexer um pouco em nosso interceptador e 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)
Um método invoke()tem acesso ao método que é chamado e a todos os seus argumentos (método Method, Object[] args). Em outras palavras, se chamarmos um método proxyVasia.introduce(vasia.getName()), e em vez de um método , introduce()um método for chamado invoke(), dentro desse método teremos acesso tanto ao método original introduce()quanto ao seu argumento! Como resultado, podemos fazer algo assim:
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);
   }
}
Agora adicionamos invoke()uma chamada ao método original ao método. Se tentarmos agora executar o código do nosso exemplo anterior:
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());
   }
}
então veremos que agora tudo funciona como deveria :) Saída do console: Olá! Meu nome é Vasya. Onde você pode precisar dele? Na verdade, muitos lugares. O padrão de design “proxy dinâmico” é usado ativamente em tecnologias populares... e, a propósito, esqueci de dizer que Dynamic Proxyé um padrão ! Parabéns, você aprendeu mais um! :) Proxies dinâmicos - 2Portanto, é usado ativamente em tecnologias e estruturas populares relacionadas à segurança. Imagine que você tem 20 métodos que só podem ser executados por usuários logados no 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 digitou login e senha, sem duplicar o código de verificação separadamente em cada método. Ou, por exemplo, se você deseja criar um log onde serão registradas todas as ações do usuário, isso também é fácil de fazer usando um proxy. Você pode até agora: basta adicionar código ao exemplo para que o nome do método seja exibido no console quando chamado invoke(), e você obterá um log simples do nosso programa :) Ao final da palestra, preste atenção em um importante limitação . A criação de um objeto proxy ocorre no nível da interface, não no nível da classe. Um proxy é criado para a interface. Dê uma olhada neste código:
//Создаем прокси нашего an object vasia
Person proxyVasia = (Person) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));
Aqui criamos um proxy especificamente para a interface Person. Se tentarmos criar um proxy para a classe, ou seja, mudarmos o tipo do link e tentarmos fazer o cast para a classe Man, nada funcionará.
Man proxyVasia = (Man) Proxy.newProxyInstance(vasiaClassLoader, interfaces, new PersonInvocationHandler(vasia));

proxyVasia.introduce(vasia.getName());
Exceção no thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 não pode ser convertido para Man A presença de uma interface é um requisito obrigatório. O proxy funciona no nível da interface. Por hoje é tudo :) Como material adicional sobre o tema proxies, posso recomendar um excelente vídeo e também um bom artigo . Bem, agora seria bom resolver alguns problemas! :) Vê você!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION