Concrete Logo
Hamburger button

Criando um Proxy em Runtime no Java (e no Android)

  • Blog
  • 13 de Novembro de 2015
Share

Para a maioria das pessoas, proxy é uma coisa que o pessoal de TI inventa para não deixar acessar o Facebook e companhia no trabalho. Ou seja, é um sistema intermediário que o browser acessa antes de chegar na selva da internet. Algo assim:

Poxy de rede

Poxy de rede

Em desenvolvimento também há o conceito de Proxy e até que é semelhante: em Java, Proxy é uma classe que serve para intermediar uma implementação de interface. Vejamos os detalhes com calma.

Pense, por exemplo, em um framework de requisições REST. Sei lá, um framework que permita que você defina contratos de métodos em uma interface e que possa colocar metadados nestes métodos para indicar os detalhes da chamada. Vamos ver… talvez usar anotações como @GET, @POST, @PUT, @DELETE, @Body, @Headers, @Path, @Query e etc.

Parece legal né? Até passo um exemplo do que queremos que seja nossa interface:

Olha que legal: só de olhar para as anotações (metadados) sei dizer que este método deve executar um HTTP GET para um servidor passar um path dinâmico e que vai me retornar uma lista de objetos já parseados. Top de linha!

Qualquer semelhança com o Retrofit é mera coincidência (a versão 2.0.0 vai exigir que o retorno seja sempre Call<T>)! Só que não! Mas como ele funciona por debaixo dos panos? Como ele consegue “implementar” esta interface e fazer a chamada em tempo de execução?

Este é um ótimo exemplo de utilização de Proxy! A interface é uma forma de estabelecermos um contrato de execução e funciona como um proxy para a implementação que o Retrofit cria em tempo de execução (runtime).

Não se assustem com o diagrama a seguir, mas tenho que mostrar o processo completo:

Diagrama Proxy em Java

Diagrama Proxy em Java

Eeeeeeeeitcha (disse um colega de trabalho quando apresentei o diagrama)… Mas vamos por partes.

Da esquerda para a direita o que acontece é:

  1. O usuário tem uma referência para um objeto do tipo GitHubService. Ela é o Proxy no diagrama.
  2. Ele executa o método listRepos(“usuário”) no proxy.
  3. O proxy internamente possui um InvocationHandler (Tratador de invocações de método) que é acionado quando o usuário chama qualquer método no proxy (inclusive equals(Object o) e hashCode()).
  4. O InvocationHandler chama seu único método

    que retorna um Object que deve ser igual ao retorno do método declarado no da interface, caso contrário teremos uma exceção. Além disso, ele recebe a instância do proxy (que pode ser útil para compararmos se é do tipo de uma interface específica), o método que foi invocado e um array de Object que são os parâmetros passados na invocação do método. No nosso exemplo, o proxy implementa GitHubService, o método é listRepos (o que poderíamos verificar com method.getName()) e args seria igual a new Object[]{ “usuário” }.
  5. O invocationHandler faz seu trabalho e retorna o que a interface espera. No nosso exemplo seria uma List<Repo>.

Talvez a sensação de “EEEEEEEEEEEeeeeeeeeitcha” tenha melhorado agora. Proxy’s são úteis para implementações que precisam de contexto do tempo de execução. Porém, tenham muita cautela nas implementações pois dentro do InvocationHandler para fazer qualquer coisa que não seja MUITO simples será necessário usar bastante reflexão. Você irá precisar saber os tipos dos parâmetros passados, recuperar anotações que eles possam ter ou que o próprio método possa ter entre outras configurações gerais.

Outro ponto de atenção é que debuggar isto pode ser um pouco estranho no começo. Vejamos o porquê. Para criar um Proxy usamos o seguinte método:

[code language=”java”]
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler) throws IllegalArgumentException
[/code]

Isso retorna Object, que é uma implementação de todas as interfaces passadas no array interfaces. Ou seja, nosso proxy dinâmico é de vários tipos ao mesmo tempo. Além disso, ele pode ter casos de erro que não acontecem normalmente em chamadas diretas de objetos. Imagine chamar um toString() que retorna um Integer??? Não tem como acontecer, apenas em proxies.

Por isso, é sempre bom conhecer o que a plataforma nos fornece e também como frameworks que usamos no dia a dia funcionam por baixo dos panos, porém sempre exercitem cautela ao decidir uma arquitetura de solução. Quase sempre estaremos adicionando complexidade com grande possibilidade de erros.

É isso aí pessoal! Lembrem de ler a documentação da classe Proxy. Se ficar alguma dúvida ou sugestão, não hesitem em comentar aqui no Blog!