В чем разница между динамическим прокси JDK и CGLib?

в случае Шаблон Проектирования Прокси, в чем разница между динамический Прокси JDK и сторонние API генерации динамического кода, такие как CGLib?

в чем разница между использованием обоих подходов и когда следует предпочесть одну над другой?

4 ответов


JDK Dynamic proxy может только прокси по интерфейсу (поэтому ваш целевой класс должен реализовать интерфейс, который затем также реализуется прокси-классом).

CGLIB (и javassist) может создать прокси-сервер путем подкласса. В этом случае прокси-сервер становится подклассом целевого класса. Нет необходимости в интерфейсах.

таким образом, динамические прокси Java могут прокси:public class Foo implements iFoo где CGLIB может прокси:public class Foo

EDIT:

Я должен упомянуть это, потому что javassist и CGLIB используют прокси путем подклассов, поэтому вы не можете объявить окончательные методы или сделать класс окончательным при использовании фреймворков, которые полагаются на это. Это остановит эти библиотеки от разрешения подкласса вашего класса и переопределения ваших методов.


функциональные возможности

  • прокси JDK позволяют реализовать любой набор интерфейсов при подклассах java.lang.reflect.Proxy. Любой метод интерфейса, плюсObject::hashCode, Object::equals и Object::toString затем перенаправляется в InvocationHandler.

  • cglib позволяет реализовать любой набор интерфейсов при подклассе любого не-конечного класса. Кроме того, методы могут быть переопределены необязательно, т. е. не все неабстрактные методы нужно перехватить. Кроме того, существуют различные способы реализации метода. Он также предлагает InvocationHandler класс (в другом пакете), но он также позволяет вызывать супер методы, используя более продвинутые перехватчики, например MethodInterceptor. Кроме того, cglib может повысить производительность за счет специализированных перехватов, как FixedValue. Я однажды написал сводка различных перехватчиков для cglib.

производительность различия

прокси JDK реализованы довольно наивно только с одним диспетчером перехвата,InvocationHandler. Для этого требуется отправка виртуального метода в реализацию, которая не всегда может быть встроена. Cglib позволяет создавать специализированный байтовый код, что иногда может повысить производительность. Вот некоторые сравнения для реализации интерфейса с 18 методами заглушки:

            cglib                   JDK proxy
creation    804.000     (1.899)     973.650     (1.624)
invocation    0.002     (0.000)       0.005     (0.000)

время отмечено в наносекундах со стандартным отклонением в скобках. Вы можете более подробную информацию о бенчмарке можно найти в учебник Byte Buddy's, где Byte Buddy-более современная альтернатива cglib. Кроме того, обратите внимание, что cglib больше не находится в активной разработке.


динамический прокси: динамические реализации интерфейсов во время выполнения с использованием API отражения JDK.

пример: Spring использует динамические прокси для транзакций следующим образом:

enter image description here

созданный прокси-сервер находится поверх bean. Это добавляет транснациональное поведение Бобу. Здесь прокси генерирует динамически во время выполнения с помощью API отражения JDK.

когда приложение остановлено, прокси будет уничтожен, и у нас будет только интерфейс и Боб в файловой системе.


в приведенном выше примере у нас есть интерфейс. Но в большинстве случаев реализация интерфейса не самая лучшая. Таким образом, bean не реализует интерфейс, в этом случае мы используем наследование:

enter image description here

для генерации таких прокси Spring использует стороннюю библиотеку CGLib.

CGLib (Cода Generation Library) построен поверх ASM, это в основном используется расширение bean прокси-сервера и добавляет поведение bean в прокси-методы.

примеры для динамического прокси JDK и CGLib

Весна ref


из весенней документации :

Spring AOP использует динамические прокси JDK или CGLIB для создания прокси для данного целевого объекта. (Динамические прокси JDK предпочтительны, когда у вас есть выбор).

Если целевой объект для проксирования реализует хотя бы один интерфейс, то будет использоваться динамический прокси JDK. Все интерфейсы, реализованные целевым типом, будут проксированы. Если целевой объект не реализует никаких интерфейсов, то CGLIB прокси будет создан.

Если вы хотите принудительно использовать проксирование CGLIB (например, для прокси каждого метода, определенного для целевого объекта, а не только тех, которые реализованы его интерфейсами), вы можете это сделать. Тем не менее, есть некоторые вопросы для рассмотрения:

окончательные методы не могут быть рекомендованы, так как они не могут быть переопределены.

вам понадобятся двоичные файлы CGLIB 2 на вашем пути к классам, тогда как динамические прокси доступны с JDK. Весна автоматически предупредит вас когда ему нужен CGLIB и классы библиотеки CGLIB не найдены на пути к классам.

конструктор вашего проксированного объекта будет вызываться дважды. Это естественное следствие прокси-модели CGLIB, в которой для каждого проксируемого объекта создается подкласс. Для каждого проксированного экземпляра создаются два объекта: фактический проксированный объект и экземпляр подкласса, реализующего совет. Это поведение не отображается при использовании прокси JDK. Обычно вызов конструктора проксированного типа дважды не является проблемой, так как обычно имеют место только назначения, и никакая реальная логика не реализована в конструкторе.