В предыдущей статье мы подробно представили реализацию динамического прокси, предоставляемого собственным API JDK, его реализация относительно проста, но имеет очень фатальный недостаток, то есть его можно использовать только для методов в интерфейсе. Завершите прокси, и ни метод класса делегата, ни метод родительского класса не могут быть делегированы.
CGLIB появился на свет.Это высокопроизводительный фреймворк для генерации кода, основанный на фреймворке ASM внизу.Он отлично решает проблему, что JDK-версия динамического прокси может проксировать только интерфейсный метод.
Динамический прокси-механизм CGLIB
Прежде чем представить принцип CGLIB, давайте запустим полный пример. В конце концов, обучение назначения всегда не легко сдаться.
Класс Student — это наш класс делегата, который сам наследует класс Father и реализует интерфейс Person.
Перехватчики CGLIB немного похожи на обработчики в динамических прокси JDK.
Как видите, прокси-класс, созданный CGLIB, является подклассом класса делегата, поэтому его можно привести к типу класса делегата.
Как видно из вывода, все методы проксируются.
Это одно из самых простых приложений CGLIB, вы можете скопировать код и запустить его самостоятельно, а затем мы разберем этот код по частям.
Давайте сначала посмотрим на структуру прокси-класса, сгенерированного CGLIB, установив системные свойства:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,本地磁盘路径)
Вы можете указать CGLIB, чтобы сохранить динамически сгенерированные классы прокси на указанный путь диска. Затем мы реформировали этот класс агента, есть много превосходных сторонних инструментов антикомпиляции, здесь я рекомендую его на веб-сайте, сайт может напрямую компилировать файл класса для нас.
Сеть обратного проектирования JAVA
Таким образом, вы можете найти прокси-класс, сохраненный CGLIB для вас, в указанном вами каталоге диска, вы просто загружаете его на этот веб-сайт, и вы получаете декомпилированный java-файл файла.
Сначала посмотрите на систему наследования этого прокси-класса.:
Student — это тип делегата, который нам нужно делегировать, и результирующий класс делегата напрямую наследует класс делегата. Этот небольшой дизайн прекрасно устраняет дефект одиночного прокси динамического прокси JDK.Наследуя класс делегата, все методы в интерфейсе класса делегата, все методы в родительском классе и все методы, определенные им самим, могут быть отражено, чтобы завершить их. Прокси метода завершает прокси всех методов делегированного класса.
В интерфейсе Factory определено несколько методов для установки и получения обратных вызовов, которые являются нашими перехватчиками, а часть о перехватчиках будет обсуждаться позже.
Затем в этой части программа отображает родительский класс, то есть класс делегата, и все методы, включая родительский класс класса делегата и методы в родительском интерфейсе.
В последней части переписываются все методы родительского класса, вот пример метода.
Очевидно, что прокси-класс переписывает все методы в родительском классе, и логика этих методов тоже очень проста, текущая сигнатура метода передается в качестве параметра перехватчику, который здесь также называется «обратным вызовом».
Поэтому с этой точки зрения вызов метода CGLIB аналогичен динамическому прокси JDK, и обоим нужно полагаться на обратный вызов, но здесь мы называем его перехватчиком, а в JDK он называется процессором.
Но здесь я хочу напомнить, что у каждого метода в прокси-классе есть две версии, одна — это метод с переопределенным исходным именем, а другая — соответствующий метод, который не проходит через перехватчик. Это результат работы механизма FastClass в CGLIB, просто хочу обратить ваше внимание, о FastClass будет рассказано позже.
До сих пор мы изучали базовую структуру прокси-класса, который в основном похож на динамический прокси JDK, разница в том, что прокси-класс, сгенерированный CGLIB, напрямую наследует наш прокси-класс, чтобы он мог проксировать все методы в прокси. сорт.
Поскольку все вызовы методов в прокси-классе будут перенаправлены на перехватчик, давайте посмотрим, что означают параметры этого перехватчика.
Пользовательский перехватчик очень прост, нам нужно только реализовать интерфейс InterfivereTiver и переопределить его методы для перехвата. Этот метод имеет четыре параметра, мы смотрим на каждого представляют что-то.
- obj: он представляет экземпляр объекта нашего прокси-класса.
- метод: ссылка на текущий вызываемый метод
- arg: формальный параметр для вызова метода
- прокси: он также представляет собой ссылку на текущий метод, основанный на механизме FastClass.
Мы знаем, что Method вызывает методы на основе отражения, но эффективность отражения всегда ниже, чем у прямых вызовов методов, а MethodProxy напрямую индексирует метод, основанный на механизме FastClass, и напрямую находит и вызывает метод через индекс, который небольшое улучшение производительности.
Давайте посмотрим на исходный код фабричного метода экземпляра MethodProxy:
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new MethodProxy.CreateInfo(c1, c2);
return proxy;
}
Среди них формальный параметр desc представляет дескриптор метода, c1 представляет класс, к которому принадлежит метод, значение, как правило, является нашим классом делегата, а значение, представленное c2, часто является прокси-классом, который мы генерируем. А name1 — это имя метода в классе делегата, а name2 — это имя метода в прокси-классе.
Например:
var1 = Class.forName("Main.Student");
var0 = Class.forName("Main.Student$$EnhancerByCGLIB$$56e20d66");
MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$3");
var1 — наш класс делегата, var0 — прокси-класс класса делегата, «()V» — сигнатура метода sayHello, а «CGLIB$sayHello$3» — имя метода sayHello в прокси-классе.
С этими параметрами MethodProxy может инициализировать FastClassInfo.
private static class FastClassInfo {
FastClass f1;
FastClass f2;
int i1;
int i2;
private FastClassInfo() {
}
}
А что такое FastClass, на самом деле салон немного сложноват, вот краткое введение для всех.
FastClass — это что-то вроде шаблона декоратора, он содержит внутри объект класса и помечает все методы индексом, поэтому внешние вызовы любого метода должны только предоставить значение индекса, и FastClass может быстро найти конкретный метод.
Внутренняя упаковка F1 здесь будет наша комиссия, F2 будет упаковывать наш класс агента, I1 - это индексное значение текущего метода в F1, i2 - это индексное значение текущего метода в F2.
Таким образом, вызов метода на основе FastClass также прост: вместо традиционного метода отражения можно указать индекс в методе вызова, нужно передать вызывающую сторону в метод вызова, а затем вызвать метод, вызванный отражением.
Как правило, экземпляр MethodProxy соответствует двум экземплярам FastClass: один является оболочкой для класса делегата и предоставляет индекс метода, а другой — оболочкой для прокси-класса, а также предоставляет индекс метода в прокси-классе.
Хорошо, теперь давайте протестируем всех:
Какой метод вызывается методом invoke и методом invokeSuper в MethodProxy? В классе прокси? Или в классе делегата?
ответ:Метод вызова вызовет последний, а метод invokeSuper вызовет первый.
Многих это может еще немного смутить, на самом деле все очень просто: экземпляр FastClass будет привязан к типу класса, а все методы в классе будут проиндексированы.
Затем, согласно тому, что мы сказали, f1 привязан к нашему классу делегата, f2 привязан к нашему прокси-классу, и независимо от того, используете ли вы f1 или f2 для вызова этого метода вызова, вам нужно передать экземпляр obj, и этот экземпляр является нашим. экземпляр прокси-класса, потому что сигнатура метода, соответствующая f1.i1, — «public final void run», а сигнатура метода, соответствующая f2.i2, — «final void CGLIB$0».
Следовательно, f1.i1.invoke и f2.i2.invoke вызывают разные методы одного и того же экземпляра, что также объясняет, почему каждый метод прокси-класса, созданного CGLIB, имеет две формы, но лично я считаю, что конструкция немного бесполезна, и легко вызвать бесконечный цикл, что усложняет понимание.
И метод вызова этого FastClass не такой уж и загадочный:
Не думайте, что это слишком сложно, экземпляр FastClass просто сканирует основные методы внутреннего типа класса, перечисляет параметры переключения регистра в методе вызова, и каждый вызов вызова должен сначала сопоставить индекс, а затем позволить целевому объекту напрямую вызвать целевой метод.
Так что здесь будет проблема, проблема бесконечного цикла. Наши перехватчики обычно пишутся так:
System.out.println("Before:" + method);
Object object = proxy.invokeSuper(obj, arg);
System.out.println("After:" + method);
return object;
invokeSuper вызовет метод "final void CGLIB$0", косвенно вызывая соответствующий метод класса делегата. И если вы измените вызов, например:
System.out.println("Before:" + method);
Object object = proxy.invoke(obj, arg);
System.out.println("After:" + method);
return object;
В результате получается бесконечный цикл, почему?
Метод вызова вызывает метод с той же сигнатурой, что и метод в классе делегата.Когда он, наконец, переходит к нашему прокси-классу, он снова проходит через перехватчик, а перехватчик продолжает вызывать обратно, и они находятся в бесконечном цикле. .
Пока, я думаю, я представил основные принципы CGLIB, вам нужно разобраться с логикой и понять процесс его выполнения от начала до конца.
Слабые стороны CGLIB
Мы всегда говорим, что CGLIB решает фатальную проблему динамического прокси JDK, единого механизма прокси. Он может проксировать родительский класс и методы в себе и родительском интерфейсе, но стоит обратить внимание,Я не говорил, что все методы можно проксировать.
Максимальный недостаток CGLIB в том, что он должен наследовать нашу комиссию, так что если класс делегата модифицировать на Final, то этот агент класса CGLIB не может.
Естественно, даже если класс не является последним классом, но если есть окончательный модифицированный метод, то этот метод не может быть прокси. Это можно увидеть из исходного кода, который мы отражены. CGLIB сгенерировал прокси-класс, необходимо переписать все методы в классе делегата, а метод модификации финала не может переписать.
В общем, CGLIB уже очень хорош, и недостатки не скрывают недостатков. Почти все основные фреймворки на рынке неизбежно используют CGLIB.Я возьму вас для анализа исходного кода фреймворка в будущем.Мы увидим CGLIB в то время!
Весь код, изображения, файлы в статье хранятся в облаке на моем GitHub:
(https://github.com/SingleYam/overview_java)
Добро пожаловать в официальную учетную запись WeChat: OneJavaCoder, все статьи будут синхронизированы в официальной учетной записи.