Proxy

Objetivo

Representar um objeto complexo com um objeto mais simples.

Propósito

A palavra Proxy significa em inglês Representante , Procurador , ou seja, algo ou alguém 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 proposito 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.

Difícil de Criar

Um objeto pode ser complexo por ser difícil de criar. Pode ser porque consome muitos recursos, porque consome recursos que não estão disponíveis no momento ou porque depende de vários outros objetos. Eventualmente ele pode ser difícil 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 significa ponta,raiz ou toco no sentido em 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

Frequentemente o objeto que precisamos usar não está disponível 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 distribuídos 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 único objeto.

Imutabilidade

Temos um objeto ao qual queremos dar acesso publico, mas não queremos que o estado desse objeto seja alterado. Basicamente queremos um mecanismo que garanta que o estado é lido, mas nunca 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 dinâmico, 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 código antes, depois ou invés de executar o método do objeto real. Esta técnica é 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 passa 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 através 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 públicos 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 uma forma dinâmica. De fato, a criação de um proxy dinâmico envolve a manipulação de bytecode que não é possível através de comandos java explícitos. 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});

       }

}

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;
   }

}

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 quanto 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 família de métodos Collections.unmodifiableXXX() envolve o objeto real que passamos como argumento num proxy e impede que o objeto real seja alterado lançando uma exceção. Outro exemplo, é a família 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 técnica 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, a implementação da lógica do proxy dinâmico é feito normalmente por um mecanismo no padrão Template Method.

Bibliografia

[1] Ralph Johnson, Erich Gamma, John Vlissides, Richard Helm Design Patterns: Elements of Reusable Object-Oriented Software: Addison-Wesley (2005)

Scroll to Top