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
Person
e uma classe simples Man
que 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 Man
possui 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). O que devemos fazer em tal situação? Precisaremos de algumas coisas:
-
InvocationHandler
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).
- O objeto original e seu proxy.
Man
e 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 ClassLoader
do (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 Proxy
e 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 ClassLoader
e 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 PersonInvocationHandler
configurado 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! :) Portanto, é 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ê!
GO TO FULL VERSION