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