No caso do Proxy Design Pattern , qual é a diferença entre o Dynamic Proxy do JDK e as APIs de geração de código dinâmico de terceiros, como o CGLib ?
Qual é a diferença entre usar as duas abordagens e quando devemos preferir uma à outra?
No caso do Proxy Design Pattern , qual é a diferença entre o Dynamic Proxy do JDK e as APIs de geração de código dinâmico de terceiros, como o CGLib ?
Qual é a diferença entre usar as duas abordagens e quando devemos preferir uma à outra?
Respostas:
O proxy dinâmico do JDK pode fazer proxy apenas por interface (portanto, sua classe de destino precisa implementar uma interface, que também é implementada pela classe de proxy).
O CGLIB (e o javassist) podem criar um proxy por subclassificação. Nesse cenário, o proxy se torna uma subclasse da classe de destino. Não há necessidade de interfaces.
Portanto, os proxies Java Dynamic podem fazer proxy: public class Foo implements iFoo
onde CGLIB pode fazer proxy:public class Foo
EDITAR:
Devo mencionar que, como javassist e CGLIB usam proxy por subclassificação, esse é o motivo pelo qual você não pode declarar métodos finais ou tornar a classe final ao usar estruturas que dependem disso. Isso impediria que essas bibliotecas permitissem subclassificar sua classe e substituir seus métodos.
Diferenças na funcionalidade
Os proxies JDK permitem implementar qualquer conjunto de interfaces durante a subclasse Object
. Qualquer método de interface, mais Object::hashCode
, Object::equals
e Object::toString
é então encaminhado para um InvocationHandler
. Além disso, a interface da biblioteca padrão java.lang.reflect.Proxy
é implementada.
O cglib permite implementar qualquer conjunto de interfaces enquanto subclassifica qualquer classe não final. Além disso, os métodos podem ser substituídos opcionalmente, ou seja, nem todos os métodos não abstratos precisam ser interceptados. Além disso, existem diferentes maneiras de implementar um método. Ele também oferece uma InvocationHandler
classe (em um pacote diferente), mas também permite chamar super métodos usando interceptores mais avançados, como por exemplo a MethodInterceptor
. Além disso, o cglib pode melhorar o desempenho por interceptações especializadas, como FixedValue
. Certa vez, escrevi um resumo de diferentes interceptores para o cglib .
Diferenças de desempenho
Os proxies JDK são implementados de forma bastante ingênua com apenas um distribuidor de interceptação, o InvocationHandler
. Isso requer um envio de método virtual para uma implementação que nem sempre pode ser incorporada. O Cglib permite criar código de bytes especializado, o que às vezes pode melhorar o desempenho. Aqui estão algumas comparações para implementar uma interface com 18 métodos de stub:
cglib JDK proxy
creation 804.000 (1.899) 973.650 (1.624)
invocation 0.002 (0.000) 0.005 (0.000)
O tempo é anotado em nanossegundos com desvio padrão entre chaves. Você pode encontrar mais detalhes sobre o benchmark no tutorial do Byte Buddy , onde o Byte Buddy é uma alternativa mais moderna ao cglib. Além disso, observe que o cglib não está mais em desenvolvimento ativo.
Proxy dinâmico: implementações dinâmicas de interfaces em tempo de execução usando a API JDK Reflection .
Exemplo: O Spring usa proxies dinâmicos para transações da seguinte maneira:
O proxy gerado vem em cima do bean. Ele adiciona comportamento transnacional ao bean. Aqui, o proxy é gerado dinamicamente em tempo de execução usando a API do JDK Reflection.
Quando um aplicativo é parado, o proxy será destruído e teremos apenas interface e bean no sistema de arquivos.
No exemplo acima, temos interface. Mas na maior parte da implementação da interface não é melhor. Portanto, o bean não implementa uma interface; nesse caso, usamos herança:
Para gerar esses proxies, o Spring usa uma biblioteca de terceiros chamada CGLib .
Cglib ( C ode L eneration Lib rary) é construído no topo de ASM , este é utilizado principalmente a gerar feijão estendendo proxy e adiciona comportamento feijão nos métodos de proxy.
O Spring AOP usa proxies dinâmicos do JDK ou CGLIB para criar o proxy para um determinado objeto de destino. (Os proxies dinâmicos do JDK são preferidos sempre que você tem uma opção).
Se o objeto de destino a ser implementado em proxy implementar pelo menos uma interface, um proxy dinâmico JDK será usado. Todas as interfaces implementadas pelo tipo de destino serão enviadas por proxy. Se o objeto de destino não implementar nenhuma interface, um proxy CGLIB será criado.
Se você deseja forçar o uso de proxy CGLIB (por exemplo, para proxy de todos os métodos definidos para o objeto de destino, não apenas aqueles implementados por suas interfaces), você pode fazê-lo. No entanto, existem alguns problemas a serem considerados:
métodos finais não podem ser aconselhados, pois não podem ser substituídos.
Você precisará dos binários CGLIB 2 em seu caminho de classe, enquanto proxies dinâmicos estão disponíveis com o JDK. O Spring avisará você automaticamente quando precisar do CGLIB e as classes da biblioteca CGLIB não forem encontradas no caminho de classe.
O construtor do seu objeto proxy será chamado duas vezes. Essa é uma consequência natural do modelo de proxy CGLIB, pelo qual uma subclasse é gerada para cada objeto em proxy. Para cada instância em proxy, são criados dois objetos: o objeto em proxy real e uma instância da subclasse que implementa o aviso. Esse comportamento não é exibido ao usar proxies JDK. Normalmente, chamar o construtor do tipo proxy duas vezes não é um problema, pois geralmente existem apenas atribuições ocorrendo e nenhuma lógica real é implementada no construtor.