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

A ideia do padrão Proxy é simples,  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 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.

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 invocation handler 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.

 

Padrão Factory

A criação de objetos pode ser algo extremamente complicado tanto que há vários padrões de projetos que tentam resolver de forma elegante e padronizada alguns destes problemas. Estes padrões de projetos são chamados de padrões de criação. A linguagem Java utiliza alguns destes padrões em várias APIs, hoje vamos discutir um pouco do padrão Factory.

No Java 7 fomos introduzidos à um conjunto de APIs denominada NIO.2 que define interfaces e classes para manipulação de arquivos. Em especial, a versão introduziu a interface java.nio.file.Path que visa substituir a API java.io.File para manipulação de arquivos.

Quando trabalhamos com sistemas de arquivos, temos de manipular arquivos e diretórios, seja por caminhos absolutos ou relativos, mas também temos diversas particularidades entre um sistema de arquivos e outro. Por exemplo, no Linux o caminho é absoluto se ele começar com / mas no Windows isso não é verdade. No Windows, o caminho é absoluto se ele começar com uma referência à uma partição, algo como C:.

É por esta razão que a API criada nos disponibiliza uma interface ao invés de uma classe. A implementação irá variar de sistema de arquivos para sistema de arquivos. Sendo assim, Como podemos escrever um código que cria objeto cujo o tipo específico só poderá ser conhecido em tempo de execução?

Seria uma complexidade desnecessária deixar a responsabilidade para o desenvolvedor e para simplificar a criação de uma instância de java.io.file.Path foi criada uma fábrica de Path chamada java.io.file.Paths.

Path path1 = Paths.get("C:\\lucas");		
Path path2 = Paths.get("/home/lucas");

System.out.println(path1.isAbsolute());
System.out.println(path2.isAbsolute());

//Output no Linux:
//false
//true

Perceba que o resultado deste método vai depender do sistema de arquivos sob o qual a JVM está executando e toda a complexidade possivelmente envolvida na criação de cada objeto Path é encapsulada pela fábrica.Algumas características de fábricas no Java são a utilização de métodos estáticos. Alguns exemplos de fábricas são java.nio.file.FileSystems e java.util.concurrent.Executors.

Perceba que existe uma nomenclatura comum nestas classes que terminam com um s no final. Mas isso não é um padrão. Ainda no NIO.2 temos uma classe java.nio.file.Files que é considerada uma classe utilitária. Classes utilitárias também são compostas por métodos estáticos mas diferem das fábricas por não criar objetos mas por manipular ou criar novos objetos de instâncias existentes.

Path path = Paths.get("/home/lucas");
Files.exists(path);

//Output no Linux:
//true

No exemplo acima, utilizamos a classe Files para chamar o método estático exists que opera em instâncias de java.nio.file.Path para verificar se o arquivo/diretório existe no sistema de arquivos.

Considerações finais

Reconhecer os padrões de projetos utilizados na linguagem é uma ótima forma de melhorar suas atividades de codificação. O padrão Factory é utilizado desde APIS SE até EE. Neste technote discutimos um pouco sobre a aplicação do padrão Factory na API NIO.2 e apresentamos algumas as operações que você costumava fazer com java.io.File e que é possível realizar de forma mais simplificada com esta API.

Até mais!