Submarino.com.br

Proxy

Objetivo

Representar um objeto complexo, por um objeto mais simples.

Propósito

A palavra Proxy significa em inglês Representante, Procurador, ou seja, algo ou alguem que se apresenta em nome de outrem com poder e autoridade de agir em seu nome ou por ele. Este conceito é essencial para entender o padrão Proxy e porque ele é tão utilizado ao ponto de ter suporte na API padrão.

O próposito do Proxy é representar um objeto mais complexo através de um objeto mais simples. Um objeto pode ser complexo por várias razões. Para cada uma delas temos um tipo de Proxy.

Dificil de Criar

Um objeto pode ser complexo por é dificil de criar. Pode ser porque consome muitos recursos, porque consome recursos que não estão disponiveis no momento ou porque dependende de vários outros objetos. Eventualmente ele pode ser dificil de criar porque ainda não definimos as suas responsabilidades ou o seu funcionamento.

Neste caso podemos criar um objeto no padrão Proxy, um proxy. Neste caso o proxy é um objeto que não tem implementação interna, ou tem uma muito simples apenas para poder ser usado em vez do objeto real. Estes tipos de proxy são chamados stub, que signfica ponta, raiz ou toco no sentido que é apenas "um pedaço de algo maior".

Pesado de Criar

O objeto que queremos é complexo porque ele é uma coleção ou algum tipo de agregado de outros objetos e criá-lo significa ter que carregar todos esses outros objetos. Neste caso queremos protelar a criação deste objeto até que seja realmente necessário. Este processo é conhecido por lazy loading e o proxy como um lazy proxy.

O mecanismo de lazy loading permite que objetos com muitas relações a outros objetos possam ser usados sem consumir demasiada memoria ou outro tipo de recursos do ambiente. Este tipo de proxy se tornou famoso em API como o Hibernate e mais tarde a JPA que protelam o carregamento de relações "um para muitos" ou "muitos para muitos" do objeto persistido.

Presença remota

Frequentementeo objeto que precisamos usar não está disponivel no nosso ambiente e precisamos acessá-lo remotamente através de algum protocolo. Isto é comum hoje em dia no ambiente de Web Services mas começou com o conceito de objetos distribuidos em CORBA e depois EJB.

Neste caso falamos em proxy remoto ou em stub remoto. A implementação deste tipo de proxy acaba sendo um desafio em si mesmo pois temos que abstrair o uso de um procolo de comunicação a partir de um unico objecto.

Imutabilidade

Temos um objeto ao qual queremos dar acesso publico, mas não queremos que o estado desse objeto seja alterado. Básciamente queremos um mecanismo que garanta que o estado é lido, mas não alterado (read only). Um proxy pode ser criado de forma a representar o objeto real, mas que intercepta todas as chamadas que possam alterar o estado do objeto real.

Alteração dinâmica de funcionalidade

Um proxy , especialmente um proxy dinamico, pode ser utilizado para adicionar funcionalidade ao objeto base. Todas as invocações aos métodos do objeto real são interceptadas. O proxy decide então se realmente delega ao objeto real a invocação do método, ou não. Além disso o proxy pode decidir executar codigo antes, depois ou inves de executar o método do objeto real. Esta tecnica é a base da chamada Programação Orientada a Aspetos (AOP). O Proxy é criado em volta do objeto real.

Implementação

A implementação do padrão Proxy depende do tipo do objeto que queremos representar. Se esse objeto é na realidade uma implementação de uma interface a implementação do proxy passar por implementar essa interface novamente. Esta nova implementação pode ser estática ou dinâmica. Na implementação estática o programador explicitamente programa uma nova classe que implementa a interface de interesse. Na implementação dinâmica usamos uma API que permite que a classe seja implementada em runtime e o controle seja feito atravez de um protocolo especial.

Se o objeto que queremos representar é a instancia de uma classe a técnica não é muito diferente. Implementamos uma nova classe que herda da primeira - implementação estática - ou utilizamos uma API para fazer isso dinamicamente. O problema aqui é que nem sempre a classe pode ser herdada. Por isto classes finais como String não podem ter proxies. Classes que não têm construtores publicos ou, pelo menos, protegidos, também não podem já que não é possivel estabelecer uma relação de herança nessas condições.

Proxy Diâmicos de Interface

Um proxy dinamico de interface é interessante quando não sabemos a interface base para o Proxy.

A API padrão nos oferece a classe java.lang.reflect.Proxy que contém toda a tecnologia necessária para a criação do proxy. De fato, a criação de um proxy dinamico involve a manipulação de bytecode que não é possivel através de comandos java explicitos. Esse mecanismo é o que a classe java.lang.reflect.Proxy esconde.

Para definirmos o proxy dinâmico precisamos de duas coisas: saber qual a classe da qual queremos um proxy e de um java.lang.reflect.InvocationHandler. Esta é a classe que nos permite controlar o que acontece no proxy. Para demonstrar isto vamos criar um proxy que calcula e escreve na tela o tempo que cada método demora a executar. Primeiro criamos uma classe que cria os Proxy em volta de objetos reais, utilizaremos a interface Collection como exemplo.

public class ChronometerProxyFactory {

       public Collection createChronometedColecctionFrom ( Collection realCollection){
       		ChronometerInvocationHandler h = new ChronometerInvocationHandler(realCollection); 
 			return Proxy.newProxyInstance(realCollection.getClass().getClassLoader(), new Class[]{Collection.class, h});      
       
       }

}
	
Código 1:

Repare que ,primeiro, criamos um objeto do tipo ChronometerInvocationHandler esta será a nossa classe de controle onde escreveremos a logica do proxy. Depois usamos a classe java.lang.reflect.Proxy para criar o proxy. Veja que para criar o proxy temos que fornecer um ClassLoader, isto é porque, como foi dito, o proxy é criado pela manipulação de bytecode que é carregado directamente pelo ClassLoader fornecido.

Outra coisa interessante é que podemos definir mais do que uma interface para o proxy. Isto corresponde a criar uma classe que implementa várias interfaces. O mesmo InvocationHandler seria responsável por responder a todas as invocaçãos de métodos de qualquer uma dessas interfaces.

Vejamos como é o objeto ChronometerInvocationHandler.

public class ChronometerInvocationHandler implements  InvocationHandler {

   private Collection realCollection;
   public ChronometerInvocationHandler(Collection realCollection){
   	this.realCollection = realCollection;
   }
   
   public Object invoke(Object proxy, Method method, Object[] args) {
   		long timeStamp = System.nanoTime();
   		
   		Object result = method.invoke(realCollection, args); // invocamos o método 
   		
   		long elapsed = System.nanoTime() - timeStamp; // tempo que demorou
   		
   		System.out.println("O método " + method.getName() + " demorou " + elapsed + " ns a executar");
   		return result;
   }
   
}
	
Código 2:

Primeiro forçamos que a coleção real seja passada no construtor. Depois, implementamos o método invoke de java.lang.reflect.InvocationHandler. Os argumentos deste método são simples de entender. O argumento proxy é o objeto proxy que foi criado dinamicamente por java.lang.reflect.Proxy. O argumento method é o método que foi chamado e args os argumentos que foram passados ao método. Repare que tanto o método invoke de InvocationHandler quando o de Method retornam um objeto, isto é assim mesmo que originalmente o método tenha sido declarado como void.

O que estamos fazendo é, antes de invocar o método no objeto real memorizar o tempo atual em nano segundos. Depois invocamos o método. Por fim calculamos quando tempo passou e impimimos isso para o console. Repare que na invocação do método ela é feita sobre o objeto real que temos como atributo. Não podemos invocar sobre o objeto proxy que recebemos como parametro, pois isso iria chamar de novo o método invoke e entrar num ciclo de chamadas recursivas que acabariam num StackOverflowError.

Discussão

A implementação de um proxy depende do objetivo do proxy e do tipo de hierarquia a que pertece o objeto que queremos representar. A criação de proxies de forma dinâmica implica na criação de bytecode em runtime. Para objetos que pertecem a uma hierarquia de interfaces a tarefa de criar proxies dinamicos é possivel através pela própria API padrão java. Para objetos que pertecem a uma hierarquia de classe a tarefa é mais complexa e não ha suporte padrão para ela ainda. Nesse caso podemos fazer uso de bibliotecas de manipulação de bytecode como ASM, CGLib ou Javaassist que permitem criar proxies de classes.

A implementação de proxies de forma estática implica criar uma classe que herda ou implementa o mesmo tipo da classe do objeto que queremos representar e implementar o código de cada método explicitamente. Embora dê muito mais trabalho, não temos que manipular bytecode e portanto podemos usar essa tecnica para quando o objeto é definido através de uma classe. Esta forma de implementação é muito semelhante à de um Decorator ou de um Adapter.

Exemplos na API padrão

Na API padrão não temos exemplos de proxies dinâmicos (afinal eles são dinâmicos), mas temos a classe java.lang.reflect.Proxy que nos permite criar proxies dinâmicos baseados em interfaces de forma simples.

Podemos encontrar na API padrão exemplos de proxies estáticos na classe java.util.Collections. A familia de métodos Collections.unmodifiableXXX() envolve o objeto real que passamos como argumento num proxy que impede que o objeto real seja alterado lança uma exceção. Outro exemplo é a familia de métodos Collections.singletonXXX() que cria um proxy de um tipo de coleção onde só existe um elemento. Os objetos retornados por estes métodos pertencem a classes internas da classe Collection que simplesmente são instanciadas.

Padrões associados

O padrão Proxy é facilmente associado ao padrão Adapter e ao padrão Decorator e muito facilmente confundidos com eles. O objeto proxy atua como Adapter quando ele representa um objeto com um contrato diferente fazendo um trabalho de tradução (pense em um seu representante legal que tem que falar em francês em vez de português).
O objecto proxy atua como Decorator quando adiciona novas funcionalidades ao objeto representado (pense num seu representante legal que sabe responder perguntas sobre leis , que você não saberia). O padrão Decorator é ainda importante porque a mesma tecnica de programação é usada tanto para implementar Decorator quando para implementar um objeto proxy sendo origem de muita confusão sobre a diferença entre eles.

Porque o padrão Proxy nos permite simular um objeto e fornecer mecanismos de lazy loading, podemos pensar nele como uma forma do padrão Flyweight.

Para criar os proxies normalmente usamos um objeto no padrão Factory e como vimos, o controle do proxy dinamico é feito normalmente por um mecanismo no padrão Template Method.