Proxies dinâmicos em Java
1. Introdução
Este artigo é sobreJava’s dynamic proxies - que é um dos principais mecanismos de proxy disponíveis para nós no idioma.
Simplificando, proxies são frentes ou invólucros que passam a chamada de função através de suas próprias instalações (geralmente em métodos reais) - potencialmente adicionando alguma funcionalidade.
Os proxies dinâmicos permitem que uma única classe com um único método atenda várias chamadas de método a classes arbitrárias com um número arbitrário de métodos. Um proxy dinâmico pode ser considerado uma espécie deFacade, mas pode fingir ser uma implementação de qualquer interface. Na capa,it routes all method invocations to a single handler - o métodoinvoke().
Embora não seja uma ferramenta destinada a tarefas de programação diárias, proxies dinâmicos podem ser bastante úteis para escritores de framework. Também pode ser usado nos casos em que implementações de classes concretas não serão conhecidas até o tempo de execução.
Esse recurso é incorporado ao JDK padrão, portanto, nenhuma dependência adicional é necessária.
2. Manipulador de invocação
Vamos construir um proxy simples que não faz nada, exceto imprimir qual método foi solicitado para ser invocado e retornar um número codificado.
Primeiro, precisamos criar um subtipo dejava.lang.reflect.InvocationHandler:
public class DynamicInvocationHandler implements InvocationHandler {
private static Logger LOGGER = LoggerFactory.getLogger(
DynamicInvocationHandler.class);
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
LOGGER.info("Invoked method: {}", method.getName());
return 42;
}
}
Aqui, definimos um proxy simples que registra qual método foi invocado e retorna 42.
3. Criação de instância proxy
Uma instância de proxy atendida pelo manipulador de invocação que acabamos de definir é criada por meio de uma chamada de método de fábrica na classejava.lang.reflect.Proxy:
Map proxyInstance = (Map) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(),
new Class[] { Map.class },
new DynamicInvocationHandler());
Depois de termos uma instância de proxy, podemos invocar seus métodos de interface normalmente:
proxyInstance.put("hello", "world");
Como esperado, uma mensagem sobre o métodoput() sendo invocado é impressa no arquivo de log.
4. Manipulador de invocação por meio de expressões lambda
ComoInvocationHandler é uma interface funcional, é possível definir o manipulador embutido usando a expressão lambda:
Map proxyInstance = (Map) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(),
new Class[] { Map.class },
(proxy, method, methodArgs) -> {
if (method.getName().equals("get")) {
return 42;
} else {
throw new UnsupportedOperationException(
"Unsupported method: " + method.getName());
}
});
Aqui, definimos um manipulador que retorna 42 para todas as operações get e lançaUnsupportedOperationException para todo o resto.
É invocado exatamente da mesma maneira:
(int) proxyInstance.get("hello"); // 42
proxyInstance.put("hello", "world"); // exception
5. Exemplo de proxy dinâmico de tempo
Vamos examinar um cenário potencial do mundo real para proxies dinâmicos.
Suponha que queremos registrar quanto tempo nossas funções levam para serem executadas. Nesse sentido, primeiro definimos um manipulador capaz de agrupar o objeto "real", rastreando informações de tempo e invocação reflexiva:
public class TimingDynamicInvocationHandler implements InvocationHandler {
private static Logger LOGGER = LoggerFactory.getLogger(
TimingDynamicInvocationHandler.class);
private final Map methods = new HashMap<>();
private Object target;
public TimingDynamicInvocationHandler(Object target) {
this.target = target;
for(Method method: target.getClass().getDeclaredMethods()) {
this.methods.put(method.getName(), method);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long start = System.nanoTime();
Object result = methods.get(method.getName()).invoke(target, args);
long elapsed = System.nanoTime() - start;
LOGGER.info("Executing {} finished in {} ns", method.getName(),
elapsed);
return result;
}
}
Posteriormente, esse proxy pode ser usado em vários tipos de objetos:
Map mapProxyInstance = (Map) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(), new Class[] { Map.class },
new TimingDynamicInvocationHandler(new HashMap<>()));
mapProxyInstance.put("hello", "world");
CharSequence csProxyInstance = (CharSequence) Proxy.newProxyInstance(
DynamicProxyTest.class.getClassLoader(),
new Class[] { CharSequence.class },
new TimingDynamicInvocationHandler("Hello World"));
csProxyInstance.length()
Aqui, fizemos um proxy de um mapa e uma sequência de caracteres (String).
As invocações dos métodos de proxy delegarão para o objeto agrupado, além de produzir instruções de log:
Executing put finished in 19153 ns
Executing get finished in 8891 ns
Executing charAt finished in 11152 ns
Executing length finished in 10087 ns
6. Conclusão
Neste tutorial rápido, examinamos os proxies dinâmicos do Java, bem como alguns de seus possíveis usos.
Como sempre, o código nos exemplos pode ser encontradoover on GitHub.