Qual é a diferença entre o proxy dinâmico JDK e o CGLib?

147

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?

KDjava
fonte
3
Obtenha o código aqui: < gist.github.com/ksauzz/1563486 >. No cglib, você pode criar proxy de classe e proxy de interface. O Spring usa CGlib por padrão, enquanto o AspectJ usa o proxy Java. Leia também: jnb.ociweb.com/jnb/jnbNov2005.html ;)
Subhadeep Ray

Respostas:

185

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

raphaëλ
fonte
obrigado..!! mas seria útil se você pudesse me dar um exemplo de código (ou Link) para ilustrar o uso de alguém em detrimento de outro em alguns casos .. !!!
KDjava
1
Observe que os proxies do JDK estão na verdade substituindo o proxy do IFoo, não para nenhum tipo de Foo. É uma distinção bastante importante. Além disso, os proxies cglib são subclasses completas - tire vantagem disso! Use filtros apenas para métodos de proxy de seu interesse e use a classe gerada diretamente.
Lscoughlin
9
Também deve ser observado que a criação da subclasse CGLib requer conhecimento suficiente sobre a superclasse para poder chamar o construtor correto com os argumentos corretos. Ao contrário do proxy baseado em interface, que não se importa com construtores. Isso torna o trabalho com proxies CGLib menos "automático" que os proxies JDK. Outra distinção está no custo da "pilha". Um proxy JDK sempre incorre em quadros de pilha extras por chamada, enquanto um CGLib pode não custar quadros de pilha extras. Isso se torna cada vez mais relevante quanto mais complexo o aplicativo fica (porque quanto maior a pilha, mais segmentos de memória consomem).
Raio
1
cglib não pode de proxy métodos finais, mas não vai jogar exceção gist.github.com/mhewedy/7345403cfa52e6f47563f8a204ec0e80
Muhammad Hewedy
Sim, o CGLIB simplesmente ignora os métodos finais.
yashjain12yj
56

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::equalse 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 InvocationHandlerclasse (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.

Rafael Winterhalter
fonte
2
Por que a documentação do Spring favorece o proxy do JDK sobre o cglib, considerando os benefícios de desempenho deste último? docs.spring.io/spring/docs/2.5.x/reference/…
P4ndaman
2
O Cglib é uma dependência externa e atualmente não é suportada. Contar com software de terceiros é sempre uma aposta, portanto é melhor quando o menor número possível de pessoas confiar nele.
Rafael Winterhalter
No seu blog, você diz: "No entanto, você deve ter cuidado ao chamar um método no objeto proxy que acompanha o método InvocationHandler # invoke. Todas as chamadas nesse método serão despachadas com o mesmo InvocationHandler e, portanto, podem resultar em um loop infinito . " O que você quer dizer?
Koray Tugay
Se você chamar um método no objeto proxy, qualquer chamada será roteada, nosso manipulador de chamadas. Se qualquer chamada do manipulador de chamada delegar para uma chamada para o objeto, a recursão mencionada ocorrerá.
Rafael Winterhalter 28/10
Olá Rafael, mensagem não relacionada à sua resposta, estou falando sobre uma edição feita há 5 anos . Como o cglib aparentemente ainda tem confirmação em 2019 e não mostra nenhum desenvolvimento interrompido em seu leia-me, removi sua declaração do trecho da tag. Fique à vontade para melhorar a descrição / trecho da tag, se houver algo relevante a ser mencionado.
Cœur
28

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:

insira a descrição da imagem aqui

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:

insira a descrição da imagem aqui

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.

Exemplos para proxy dinâmico JDK e CGLib

Ref Primavera

Premraj
fonte
5

Da documentação do Spring :

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.

Taras Melnyk
fonte