Padrões de Proxy em Java: Uma Análise de Implementações Estáticas e Dinâmicas

  1. Proxy Estático

O padrão de proxy estático envolve a criação manual de uma classe proxy para uma classe de serviço. Tanto o serviço quanto seu proxy geralmente implementam uma interface comum, garantindo que o proxy possa ser usado onde o serviço original é esperado. O proxy delega chamadas de método para o objeto de serviço real, adicionando funcionalidades extras antes ou depois da execução do método delegado.

Primeiro, definimos uma interface para o nosso serviço:


// Interface principal para operações de dados
public interface OperacoesDados {
    String obterInfoPrincipal();
    String obterInfoSecundaria();
}

Em seguida, criamos a implementação do nosso serviço alvo:


// Classe alvo que implementa a interface
public class GerenciadorDadosImpl implements OperacoesDados {

    @Override
    public String obterInfoPrincipal() {
        return "Informação essencial do sistema.";
    }

    @Override
    public String obterInfoSecundaria() {
        return "Dado confidencial da empresa.";
    }
}

Agora, desenvolvemos a classe proxy estática. Esta classe também implementa a interface OperacoesDados e contém uma referência ao objeto de serviço real (o "alvo"). Ela pode interceptar chamadas de método e aplicar lógica adicional. Neste exemplo, vamos adicionar um log e modificar o retorno de um método.


public class GerenciadorDadosLogProxy implements OperacoesDados {
    private OperacoesDados alvo;

    // Construtor para injetar o alvo
    public GerenciadorDadosLogProxy(OperacoesDados alvo) {
        this.alvo = alvo;
    }

    @Override
    public String obterInfoPrincipal() {
        System.out.println("[PROXY ESTÁTICO] Acessando informação principal...");
        return alvo.obterInfoPrincipal(); // Delega para o alvo
    }

    @Override
    public String obterInfoSecundaria() {
        System.out.println("[PROXY ESTÁTICO] Acessando e modificando informação secundária...");
        String resultadoOriginal = alvo.obterInfoSecundaria();
        // Adiciona um prefixo ao resultado original
        return "PROCESSADO: " + resultadoOriginal.toUpperCase(); 
    }
}

Para testar o proxy estático:


public class ExemploProxyEstatico {
    public static void main(String[] args) {
        // Objeto de serviço original
        OperacoesDados servicoOriginal = new GerenciadorDadosImpl();

        // Objeto proxy, envolvendo o serviço original
        OperacoesDados servicoProxy = new GerenciadorDadosLogProxy(servicoOriginal);

        String info1 = servicoProxy.obterInfoPrincipal();
        String info2 = servicoProxy.obterInfoSecundaria();

        System.out.println("Resultado Info Principal: " + info1);
        System.out.println("Resultado Info Secundária: " + info2);
    }
}

A execução do código demonstrará que o método obterInfoPrincipal exibe uma mensagem de log antes de retornar o valor, e o método obterInfoSecundaria tem seu retorno modificado (em maiúsculas e com prefixo) pelo proxy.

  1. Proxy Dinâmico com JDK

Ao contrário dos proxies estáticos, os proxies dinâmicos são criados em tempo de execução usando a API de reflexão do Java. Eles ainda exigem que o objeto alvo implemente uma ou mais interfaces. O Java Development Kit (JDK) fornece o java.lang.reflect.Proxy e java.lang.reflect.InvocationHandler para criar esses proxies.

Utilizaremos a mesma interface OperacoesDados e a implementação GerenciadorDadosImpl do exemplo de proxy estático.

2.1. Exemplo Básico de Monitoramento

Aqui, criaremos um InvocationHandler que registra o tempo de execução de cada método e também transforma o resultado do método obterInfoSecundaria.


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class AuditoriaDadosHandler implements InvocationHandler {
    private final Object alvo;

    public AuditoriaDadosHandler(Object alvo) {
        this.alvo = alvo;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long inicio = System.nanoTime();
        System.out.println(" [PROXY JDK] Invocando método: " + method.getName());

        // Delega a chamada para o objeto alvo
        Object resultado = method.invoke(alvo, args);

        long fim = System.nanoTime();
        System.out.println(" [PROXY JDK] Método " + method.getName() + " executado em " + (fim - inicio) / 1_000_000.0 + " ms.");

        // Lógica de aprimoramento condicional
        if ("obterInfoSecundaria".equals(method.getName()) && resultado instanceof String) {
            return "AUDITADO: " + ((String) resultado).toUpperCase();
        }
        return resultado;
    }
}

Classe de teste para o proxy dinâmico JDK:


import java.lang.reflect.Proxy;

public class ExemploAuditoriaDados {
    public static void main(String[] args) {
        // Objeto de serviço original
        OperacoesDados servicoOriginal = new GerenciadorDadosImpl();

        // Criando o proxy dinâmico
        OperacoesDados servicoProxy = (OperacoesDados) Proxy.newProxyInstance(
            servicoOriginal.getClass().getClassLoader(), // ClassLoader do alvo
            servicoOriginal.getClass().getInterfaces(),  // Interfaces implementadas pelo alvo
            new AuditoriaDadosHandler(servicoOriginal)   // Nosso InvocationHandler
        );

        System.out.println("\n--- Chamadas através do Proxy JDK ---");
        String info1 = servicoProxy.obterInfoPrincipal();
        String info2 = servicoProxy.obterInfoSecundaria();

        System.out.println("Info Principal Retornada: " + info1);
        System.out.println("Info Secundária Retornada: " + info2);
    }
}

2.2. Exemplo de Gestão de Ciclo de Vida de Equipamento

Este exemplo simula a adição de passos de preparação e finalização em torno de uma operação principal, similar ao cenário de "ligar/desligar" um equipamento ou "abastecer/lavar" um carro do exemplo original.

Interface do equipamento:


public interface Equipamento {
    void iniciar();
    void executarOperacao();
    void parar();
}

Implementação do equipamento alvo:


public class RoboIndustrial implements Equipamento {
    @Override
    public void iniciar() {
        System.out.println("  Robô Industrial: Ligando sistemas...");
    }

    @Override
    public void executarOperacao() {
        System.out.println("  Robô Industrial: Executando tarefa principal (soldagem)...");
    }

    @Override
    public void parar() {
        System.out.println("  Robô Industrial: Desligando e hibernando.");
    }
}

O InvocationHandler para gerenciar o ciclo de vida:


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class GerenciadorCicloEquipamento implements InvocationHandler {
    private Equipamento equipamentoAlvo;

    public GerenciadorCicloEquipamento(Equipamento equipamentoAlvo) {
        this.equipamentoAlvo = equipamentoAlvo;
    }

    // Método que cria e retorna o proxy
    public Equipamento criarProxy() {
        return (Equipamento) Proxy.newProxyInstance(
            equipamentoAlvo.getClass().getClassLoader(),
            equipamentoAlvo.getClass().getInterfaces(),
            this
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("executarOperacao".equals(method.getName())) {
            System.out.println(" [PROXY CICLO] Realizando verificações pré-operação...");
            Object resultado = method.invoke(equipamentoAlvo, args); // Chama o método real
            System.out.println(" [PROXY CICLO] Executando rotina de limpeza pós-operação.");
            return resultado;
        }
        // Para outros métodos, apenas chama o método real
        return method.invoke(equipamentoAlvo, args);
    }
}

Classe de teste para o gerenciador de ciclo:


public class TesteRoboIndustrialProxy {
    public static void main(String[] args) {
        Equipamento robo = new RoboIndustrial();
        GerenciadorCicloEquipamento gerenciador = new GerenciadorCicloEquipamento(robo);
        Equipamento roboProxy = gerenciador.criarProxy();

        System.out.println("\n--- Testando o Robô com Proxy ---");
        roboProxy.iniciar();
        roboProxy.executarOperacao();
        roboProxy.parar();
    }
}

O resultado mostrará que, ao chamar executarOperacao no proxy, as mensagens adicionais de "verificações pré-operação" e "rotina de limpeza pós-operação" são exibidas, demonstrando a injeção de lógica.

  1. Proxy Dinâmico com CGLIB

Uma limitação fundamental dos proxies dinâmicos do JDK é que eles exigem que a classe alvo implemente uma interface. Se uma classe não implementa nenhuma interface ou se queremos fazer proxy de uma classe concreta diretamente, o JDK Proxy não pode ser usado. Para esses cenários, entra em cena o CGLIB (Code Generation Library).

CGLIB é uma biblioteca de geração de código que permite estender classes e implementar interfaces em tempo de execução. Para criar um proxy, o CGLIB gera uma subclasse da classe alvo e sobrescreve seus métodos. Portanto, a classe alvo não pode ser final e seus métodos também não podem ser final, já que não podem ser sobrescritos.

CGLIB é uma biblioteca externa e requer a adição de sua dependência ao projeto (por exemplo, via Maven ou Gradle).

Vamos definir uma classe de serviço simples, sem nenhuma interface:


// Classe de serviço sem interface
public class ServicoRelatorio {
    public String gerarRelatorioDiario() {
        return "Relatório diário de vendas.";
    }

    public String obterStatusSistema() {
        return "Sistema operando normalmente.";
    }
}

Agora, criamos a fábrica de proxies CGLIB, que implementa net.sf.cglib.proxy.MethodInterceptor:


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class FabricaAprimoramentoCGLIB implements MethodInterceptor {
    private Object alvo;

    public FabricaAprimoramentoCGLIB(Object alvo) {
        this.alvo = alvo;
    }

    // Método para criar e retornar o objeto proxy CGLIB
    public Object criarProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(alvo.getClass()); // Define a superclasse (o alvo)
        enhancer.setCallback(this);              // Define o interceptor (esta própria classe)
        return enhancer.create();                // Cria a instância do proxy
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        System.out.println(" [PROXY CGLIB] Interceptando método: " + method.getName());

        // Delega a chamada para o objeto alvo original
        Object resultadoOriginal = method.invoke(alvo, args);

        // Adiciona lógica de aprimoramento condicional
        if ("gerarRelatorioDiario".equals(method.getName()) && resultadoOriginal instanceof String) {
            String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"));
            return resultadoOriginal + " (Gerado em: " + timestamp + ")";
        } else if ("obterStatusSistema".equals(method.getName()) && resultadoOriginal instanceof String) {
            return "STATUS DETALHADO: " + ((String) resultadoOriginal).toUpperCase();
        }

        return resultadoOriginal;
    }
}

Classe de teste para o proxy CGLIB:


public class TesteConfiguracaoCGLIB {
    public static void main(String[] args) {
        // Objeto de serviço original (sem interface)
        ServicoRelatorio relatorioOriginal = new ServicoRelatorio();

        // Criando o proxy CGLIB
        FabricaAprimoramentoCGLIB fabrica = new FabricaAprimoramentoCGLIB(relatorioOriginal);
        ServicoRelatorio relatorioProxy = (ServicoRelatorio) fabrica.criarProxy();

        System.out.println("\n--- Chamadas através do Proxy CGLIB ---");
        String relatorio = relatorioProxy.gerarRelatorioDiario();
        String status = relatorioProxy.obterStatusSistema();

        System.out.println("Relatório Gerado: " + relatorio);
        System.out.println("Status do Sistema: " + status);
    }
}

Ao executar o teste CGLIB, você verá que o método gerarRelatorioDiario retorna o relatório original com um timestamp anexado, e obterStatusSistema retorna o status original em maiúsculas com um prefixo, mesmo sem o serviço ter implementdao uma interface.

Tags: java ProxyPattern JDKDynamicProxy CGLIB reflection

Publicado em 8-5 16:40