Aplicação prática do Padrão Proxy

Dado um objeto do nosso sistema com um método foo, ao realizar uma chamada pojo.foo(), o método foo do POJO é acessado diretamente

Para aplicar o padrão Proxy precisamos inserir um objeto intermediário, que será do mesmo tipo do objeto pojo — implementando as mesmas interfaces ou herdando da mesma classe do pojo — que irá receber todas as chamadas aos métodos e realizar as devidas verificações e processamentos.

Para criar este objeto intermediário, devemos implementar uma classe com a devida equivalência de tipos ou, podemos criar dinamicamente uma classe que implemente as devidas interfaces/super classe.

Aplicação Prática do Proxy

Na linguagem Java, é possível criar os chamados proxies dinâmicos por meio da API de Proxy. Porém, esta forma apresenta algumas restrições, pois permite apenas a criação de Proxy de interfaces. Ou seja, nossos objetos devem obrigatoriamente implementar uma interface comum (Que é criada dinamicamente pelas interfaces informadas em tempo de execução).

Um cenário onde o padrão é aplicado é em um Pool de Conexões. Ao obter uma conexão de uma fonte de dados (java.sql.DataSurce), a aplicação realiza suas leituras/escritas no banco de dados e, quando não é mais necessária, fecha a conexão pela chamada ao método close da interface java.sql.Connection. A imagem abaixo apresenta o ciclo de vida de uma conexão em um pool.

Ciclo de vida de uma conexão em um pool (vlad blog)

Sendo assim, para controlar esta devolução da conexão ao pool, e não fechar a conexão de fato, é possível utilizar o padrão proxy. O exemplo de código apresentado a seguir é baseado no projeto flexy-pool.

A linha 0 1–6 temos a criação do Proxy dinâmico por meio do método estático java.lang.reflect.Proxy#newProxyInstance. O primeiro argumento é o classloader onde o proxy criado será carregado, normalmente é passado o mesmo classloader do objeto this. Já o segundo, é a lista de interfaces que serão implementadas dinamicamente pela instância do proxy. Estas interfaces definem a tipagem do objeto criado, ou seja, quais métodos serão possíveis invocar e, quais tipos ele pode ser atribuído. Em nosso exemplo, a interface javax.sql.Connection.

O último parâmetro é uma instância da interface InvocationHandler, implementada pela classe ConnectionInvocationHandler em nosso exemplo. Cada instância de Proxy deve possuir um Invocation Handler associado. Quando qualquer método é chamado em uma instância do Proxy, a chamada é encaminhada para o método invoke do Invocation Handler, que recebe informações de qual método do foi chamado e quais foram os parâmetros fornecidos. Com o uso desta classe, podemos intermediar a comunicação com o objeto encapsulado.

A implementação do método apresentado faz com que, quando um método close for chamado pela aplicação, a conexão seja devolvida ao pool. A lógica que implementa este algoritmo é delegada para uma instância de ConnectionCallback, uma interface própria do flexy-pool. Quando qualquer outro método da interface Connection for invocado, o InvocationHandler irá delegar a chamada para o objeto encapsulado, que ocorre na linha 23, via API de reflexão.

Considerações finais

É possível aplicar proxies em muitos outros cenários, para citar algumas aplicações reais, temos o módulo spring-aop, do Spring framework, que utiliza como base para criação dos Aspectos. Um exemplo bacana do uso de proxies no Spring framework é utilizado pelo projeto Spring Data para transformar nossas interfaces de Repositório — “magicamente” — em algo funcional. Por falar em “data”, o Hibernate utiliza para implementar a recuperação de informações Lazy-loading. Outro exemplo é encontrado nos frameworks de testes, tais como Mockito, utilizam proxies para criação de objetos Mocks.

Por fim, caso seja desejado criar proxies de classes, podemos fazê-lo utilizando bibliotecas de terceiros, como a cglib, javassist e byte buddy que são responsáveis por outras atividades além da criação de proxies, em especial, manipulação de bytecode.