A mágica por trás das interceptações em Java

Tinha escrito esse no LinkedIn há algum tempo. Só estou postando aqui para manter tudo junto.

Qualquer programador Java deve estar mais habituado a injetar dependências, definir métodos de callback ou, entre outras, fazer lazy load de suas entidades. Mas você já parou para pensar como isso acontece por trás dos panos? Como, por exemplo, o Hibernate permite que você defina métodos para executar antes e depois da sua entidade ser sincronizada com o banco de dados subjascente, como um container EJB/CDI consegue interceptar as chamadas de métodos para iniciar transações, executar rotinas de segurança e encapsular exceções?

Como acontece em qualquer profissão, o profissional trabalha melhor quando conhece bem a ferramenta que utiliza. E não seria diferente com os profissionais de TI. Tomar conhecimento dos mecanismos que permitem com que a mágica por trás das tecnologias aconteçam não só te tornará um profissional melhor, mas te permitirá antecipar situações inconvenientes e utilizar seguramente um componente qualquer.

Neste caso, vamos falar dos proxies.

Proxy é o nome do design pattern designado a permitir fornecer um substituto para um objeto qualquer. Esse substituto deve ser capaz de interceptar as chamadas ao objeto substituído permitindo que operações extras sejam executadas em complemento à função do objeto real.

Suponha que você tenha a classe a seguir:

public class Greetings {
   
    public String sayHello(String to) {         
        return "Olá " + to + ".";     
    } 
} 

E você tenha uma factory para os objetos dela:

public class GreetingsFactory {

    public static Greetings createGreetings() {  
       return new Greetings();
    }
} 

Quando você precisar gerar uma saudação, você pode facilmente invocar:

GreetingsFactory.createGreetings().sayHello("Jaumzera");

Que vai te retornar um belo:

Olá Jaumzera.

Agora suponha que em dado momento seu cliente decida que a saudação deva ser retornada entre tags <h1> e </h1>. É claro que no mundo real simplesmente alteraríamos a classe Greetings para fazer o desejado. Entretanto, aqui estamos para exemplificar o padrão proxy.

public class GreetingsHtmlProxy extends Greetings {
   
    @Override
    public String sayHello(String to) {         
        return "<h1> " + new Greetings().sayHello(to) + ".</h1>";     
    } 
} 

A nova classe GreetingsHtmlProxy implementa a mesma interface do proxy (que neste caso é a própria classe), delega a chamada para um objeto da classe Greetings e altera seu resultado para se adequar ao novo requisito. E então a fábrica é alterada para retornar para retornar o novo objeto saudador:

public class GreetingsFactory {

    public static Greetings createGreetings() {  
       return new GreetingsHtmlProxy();
    }
} 

O fato da classe proxy estender a classe original, fará com que não tenhamos problemas de compatibilidade em quaisquer que sejam os pontos onde a classe Greetings original estava sendo usada.

E onde antes exibia uma suadação simples, passará a exibir:

<h1>Olá Jaumzera.</h1>

Observe que utilizando a classe proxy poderíamos, por exemplo, executar ações antes ou depois do método orignal ser executado, como executar rotinas de log, iniciar e parar transações, criar callbacks para pré e pós execução, entre outras.

Concordamos no fato de que é um exemplo medíocre, mas serve bem para mostrar o papel de um objeto substituto, ou seja, um proxy.

Com a importância que esse tipo de abordagem foi ganhando, foi inserida na plataforma Java, a partir da versão 1.3, a possibilidade de criar-se versões dinâmicas do pattern proxy. Essas versões nada mais são do que implementações de runtime de interfaces Java. Pelo fato de se basearem em interfaces, vamos extrair uma interface do exemplo anterior.

public interface IGreetings {
    
    String sayHello(String to);
    
}

E alterar o objeto Greetings para implementá-la:

public class Greetings implements IGreetings {
    
    public String sayHello(String to) {
        return "Olá " + to;
    }        
}

Para criar um proxy dinâmico, neste caso, vamos implementar a interface InvocationHandler, do pacote java.lang.reflect:

public class GreetingsInvocationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        return "<h1>" + new Greetings().sayHello((String) args[0]) + "</h1>";
    }
}

A interface faz exatamente o mesmo trabalho do proxy que implementamos manualmente, ou seja, envolve o retorno do objeto original em tags <h1> e </h1>.

Para executar o exemplo, criamos uma instância do proxy a partir da classe Proxy, também do pacote `java.lang.reflect`:

IGreetings greet = (IGreetings) Proxy.newProxyInstance(
                Greetings.class.getClassLoader(),
                new Class[]{IGreetings.class},
                new GreetingsInvocationHandler());

System.out.println(greet.sayHello("Jaumzera"));

E, ao executar, podemos observar o resultado desejado.

<h1>Olá Jaumzera.</h1>

O fato é que utilizar implementações de runtime baseadas em interfaces limita um pouco a utilização da estratégia de proxy. Portanto, frameworks como o próprio Hibernate utilizam APIs mais sofisticada que permite a criação de proxies baseadas em classes concretas, como por exemplo o CGLib (https://github.com/cglib/cglib), uma vez que seus objetos de trabalho, ou seja, as entidades, são baseadas em POJOs frequentemente criados sem interface de negócio.

Para implementar o mesmo proxy anterior utilizando a API CGLib, faríamos da seguinte forma.

 Greetings greetCGLib = (Greetings) Enhancer.create(
                Greetings.class, new MethodInterceptor() {

            @Override
            public Object intercept(Object o, Method method,
                    Object[] os, MethodProxy mp) throws Throwable {
                
                return "<h1>" + mp.invokeSuper(o, os) + "</h1>";
            }
        });
        
        System.out.println(greetCGLib.sayHello("Jaumzera"));

E, ao executar, verás que o resultado é o mesmo do exemplo anterior. Também é evidente que a CGLib simplifica muito a criação dos proxies dinâmicos, uma vez que podem ser utilizadas a partir de classes concretas, em detrimento dos proxies Java, que são baseados em interfaces.

Enfim, resta dizer que a CGLib não possui uma documentação muito legal, portanto, você tem que confiar no tato e no JavaDoc da API. Além do fato de que, a menos que você esteja projetando frameworks ou qualquer coisa do tipo, sempre haverá uma solução baseada em modelos de objetos mais elegante do que apelar para introspecção de objetos ou qualquer coisa mais “baixo nível” dessas.

Até o próximo.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s