В этой статье поговорим о динамических прокси в Java.
Динамические прокси широко используются в Java, напримерПринцип реализации АОП, удаленный вызов RPC, получение объекта аннотации Java, структура ведения журнала, глобальная обработка исключений, обработка транзакций и т. д..
Прежде чем понять динамический прокси, нам нужно понять, что такое режим прокси.
прокси-режим
代理模式(Proxy Pattern)
является одним из 23 шаблонов проектирования, принадлежащих结构型模式
. Он имеет в виду, что объект сам не выполняет реальных операций, а получает желаемый результат через другие объекты. Преимущество этого в том, что вы можетеНа основе реализации целевого объекта усиливать дополнительные функциональные операции, то есть расширять функцию целевого объекта..
Здесь может быть отражена очень важная идея программирования: не меняйте исходный код по своему желанию, если вам нужно его изменить, вы можете расширить метод по доверенности.
Как показано на рисунке выше, пользователь не может напрямую использовать целевой объект, но создает прокси-объект, который используется в качестве ретранслятора.Прокси-объект отвечает за вызов реального поведения целевого объекта, чтобы вернуть результат пользователю.
То есть ключевым моментом прокси являетсяСвязь между прокси-объектом и целевым объектом.
Агент на самом деле то же самое, что и агент, например, вы звезда и у вас много поклонников. У вас большой трафик, и к вам часто приходит много спонсоров, чтобы обсудить сотрудничество и т. д. Вы, должно быть, сами перегружены, ведь вам приходится заниматься не только сотрудничеством, но и талантами, съемками, обслуживанием и фанатами.Отношения, Маркетинг, и Т. Д. С этой целью вы нашли брокера, и вы попросили его взять на себя ответственность за переговоры о сотрудничестве с мастером золота.Брокер был очень серьезным и ответственным, и он выполнил задание удовлетворительно.Поэтому, когда мастер золота попросил вас обсудить сотрудничество , это стало золотой медалью Господь говорит с вашим агентом о сотрудничестве, и у вас есть больше времени для работы над другими вещами. Как показано ниже
Это своего рода статический прокси, потому что代理(经纪人)
Это ваш собственный выбор.
Но затем, когда ваш бизнес постепенно расширялся, вы не могли выбрать каждого брокера, поэтому вы просто передавали это агентству, которое делало это за вас. Если вы хотите быть популярным на станции B, вы можете напрямую попросить агентскую компанию помочь вам найти агента, отвечающего за маркетинг.Если вы хотите поддерживать отношения с фанатами, вы можете напрямую попросить агентскую компанию найти вам питомник. , то схема отношений в это время станет такой
В настоящее время почти вся ваша работа выполняется агентской компанией, и вы не знаете, кого они посылают, чтобы помочь вам сделать эти вещи.Это зависит от реальной ситуации, потому что агентская компания несет ответственность не только за вас. звезда, и все хороши в разных областях, поэтому вы назначите соответствующего агента только после того, как у вас возникнут реальные потребности.Эта ситуация называется动态代理
.
статический прокси
От того, можно ли определить окончательный метод выполнения в период компиляции, режим прокси можно разделить на статический прокси и динамический прокси.Давайте сначала продемонстрируем динамический прокси.Здесь есть требование.Лидер хочет добавить пользователя в систему , но сам не добавляет.Пусть программисты ниже добавят и посмотрим на процесс.
Сначала создайте пользовательский интерфейс и определите метод шаблона для сохранения пользователя.
public interface UserDao {
void saveUser();
}
Создайте класс реализации пользователя, который является реальным методом для пользовательских операций.
public class UserDaoImpl implements UserDao{
@Override
public void saveUser() {
System.out.println(" ---- 保存用户 ---- ");
}
}
Создайте класс пользовательского агента. Класс пользовательского агента также имеет метод для сохранения пользователя, но этот метод относится к методу прокси. Он не выполняет реальное сохранение пользователя, но содержит реальный пользовательский объект внутри для сохранения пользователя.
public class UserProxy {
private UserDao userDao;
public UserProxy(UserDao userDao){
this.userDao = userDao;
}
public void saveUser() {
System.out.println(" ---- 代理开始 ---- ");
userDao.saveUser();
System.out.println(" ---- 代理结束 ----");
}
}
Ниже приведен метод проверки.
public class UserTest {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
UserProxy userProxy = new UserProxy(userDao);
userProxy.saveUser();
}
}
Создайте новый класс реализации пользователя (UserDaoImpl), который не выполняет действия пользователя. Затем создайте пользовательский агент (UserProxy) для выполнения сохранения пользовательского агента (saveUser), который будет вызывать метод сохранения пользователя (saveUser) класса реализации пользователя внутри, потому что наша JVM может определить окончательный метод выполнения во время компиляции, поэтому выше Этот режим прокси также называется静态代理
.
Преимущество прокси-режима в том, что он ненавязчив., если мы добавим какие-либо новые функции в будущем, мы можем напрямую добавить прокси-класс и позволить прокси-классу вызывать пользовательские операции, чтобы мы могли добавлять новые функции без изменения исходного кода. Тогда жизнь прекрасна, мы можем напрямую добавить желаемую функциональность, здесь美丽
Во времена , cxuan добавил бесчисленное количество классов агентов, таких как пользовательский агент, агент журнала и так далее. Но хорошие времена длились недолго, cxuan обнаружил, что каждый раз при изменении кода приходилось менять каждый класс прокси, что очень раздражало! Я трачу свое драгоценное время, меняя каждый класс прокси?
Динамический прокси
Динамический прокси JDK
Итак, cxuan обратился за помощью в Интернет и нашел концепцию, называемую динамическим прокси, я прочитал ее и нашел ее интересной, поэтому cxuan изменил код статического прокси и добавил новый.UserHandler
пользовательский агент и сделать что-то оtest
, код показывает, как показано ниже
public class UserHandler implements InvocationHandler {
private UserDao userDao;
public UserHandler(UserDao userDao){
this.userDao = userDao;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
saveUserStart();
Object obj = method.invoke(userDao, args);
saveUserDone();
return obj;
}
public void saveUserStart(){
System.out.println("---- 开始插入 ----");
}
public void saveUserDone(){
System.out.println("---- 插入完成 ----");
}
}
Тестовый класс выглядит следующим образом
public static void dynamicProxy(){
UserDao userDao = new UserDaoImpl();
InvocationHandler handler = new UserHandler(userDao);
ClassLoader loader = userDao.getClass().getClassLoader();
Class<?>[] interfaces = userDao.getClass().getInterfaces();
UserDao proxy = (UserDao)Proxy.newProxyInstance(loader, interfaces, handler);
proxy.saveUser();
}
UserHandler
Это класс пользовательского агента. UserDao в конструкторе является реальным объектом. Скрывая UserDao в UserHandler, реальный метод выполняется через UserDao в UserHandler.
Вы можете понимать загрузчик классов и массив интерфейсов как дерево методов, каждый конечный узел является методом, а proxy.saveUser() позади сообщает JVM, какой метод в дереве методов выполняется.
Пользовательские агенты получаются через загрузчики классов, интерфейсные массивы и прокси-классы. Метод saveUser эквивалентен указанию прокси-серверу, какой метод вы хотите выполнить в конце. Метод proxy.saveUser не является методом saveUser, который, наконец, выполняется напрямую. Окончательный метод saveUser запускается методом вызова в UserHandler.
Вышеупомянутый прокси-режим, в котором окончательный метод выполнения не может быть определен во время компиляции и может быть получен только динамически во время выполнения, называется动态代理
.
Преимущество динамических прокси заключается в реализации无侵入式
Расширение кода также может быть расширено до метода. Кроме того, это также может значительно сократить объем кода и избежать переполнения прокси-классами.
Итак, теперь мы суммируем характеристики статического прокси и динамического прокси.
статический прокси
- Статический прокси-класс: созданный программистом или сгенерированный сторонним инструментом, а затем скомпилированный, перед запуском программы файл .class прокси-класса уже существует.
- Статические прокси заранее знают, что проксировать.
- Статический прокси-класс обычно проксирует только один класс.
Динамический прокси
- Динамические прокси обычно выполняются во время работы программы через
反射机制
динамически генерируется. - Классы динамических прокси обычно прокси
接口
Все классы ниже. - Динамические прокси не знают заранее, что они проксируют, только во время выполнения.
- Обработчик вызова динамического прокси должен заранее наследовать интерфейс InvocationHandler и использовать метод newProxyInstance в классе Proxy для динамического создания класса прокси.
В приведенном выше примере кода мы определяем интерфейс UserDao, а затем есть класс реализации интерфейса UserDaoImpl.То, что мы получаем через метод Proxy.newProxyInstance, также является объектом класса реализации UserDao, так что на самом деле это своего рода изДинамический прокси на основе интерфейса. также называемыйJDK 动态代理
.
Это единственная технология динамического прокси? Поскольку вы спросили об этом, конечно, нет.
Кроме того, есть еще некоторые прокси-технологии, но они требуют загрузки дополнительных jar-пакетов, поэтому подытожим все прокси-технологии и их характеристики
-
Динамический прокси JDK прост в использовании, он встроен в JDK, поэтому нет необходимости вводить сторонние пакеты Jar.
-
И CGLIB, и Javassist являются продвинутыми библиотеками генерации байт-кода с более высокой общей производительностью, чем динамический прокси-сервер, поставляемый с JDK, и они очень мощные.
-
ASM — это низкоуровневый инструмент генерации байт-кода, использование ASM почти похоже на программирование байт-кода, которое предъявляет самые высокие требования к разработчикам. Конечно, также
性能最好
Инструмент создания динамических прокси для . Однако использование ASM очень громоздко, и производительность не улучшается на порядки.По сравнению с передовыми инструментами генерации байт-кода, такими как CGLIB, программы ASM менее удобны в сопровождении.Javassist.
Далее мы по очереди представим использование этих инструментов динамического прокси.
Динамический прокси CGLIB
Мы упоминали выше, что динамический прокси JDK — это прокси на основе интерфейса, в то время как динамический прокси CGLIBЭто реализация прокси для класса, в основном для создания подкласса для указанного класса и переопределения методов в нем., то есть динамический прокси CGLIB принимает метод наследования классов -> переписывание методов Давайте сначала посмотрим на структуру динамического прокси CGLIB.
Как показано на рисунке выше, прокси-класс наследуется от целевого класса, при каждом вызове метода прокси-класса он будет перехватываться в перехватчике, а в перехватчике будет вызываться метод целевого класса.
Ниже мы используем пример для демонстрации использования динамического прокси CGLIB.
Сначала импортируйте пакет jar, связанный с CGLIB, мы используем метод MAVEN.
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
Затем мы создаем новый класс UserService, чтобы отличить его от UserDao и UserDaoImpl выше.
public class UserService {
public void saveUser(){
System.out.println("---- 保存用户 ----");
}
}
Затем мы создаем собственный перехватчик метода, который реализует класс перехватчика
public class AutoMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("---- 方法拦截 ----");
Object object = methodProxy.invokeSuper(obj, args);
return object;
}
}
Вот объяснение того, что означают эти параметры
- Объект obj: obj — это экземпляр прокси-класса, динамически сгенерированный CGLIB.
- Метод метода: метод — это ссылка на прокси-метод, вызываемая классом сущности.
- Objectp[] args: это список параметров метода.
- MethodProxy methodProxy : это ссылка сгенерированного прокси-класса на метод.
заmethodProxy
Метод, вызываемый параметрами, имеет внутри две опции:invoke()
иinvokeSuper()
, разница между ними не объясняется в этой статье.Заинтересованные читатели могут обратиться к этой статье:Разница в анализе исходного кода Cglib между invoke и invokeSuper
Затем мы создаем тестовый класс для проверки
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new AutoMethodInterceptor());
UserService userService = (UserService)enhancer.create();
userService.saveUser();
}
Тестовый класс в основном включаетEnhancer
использования, Enhancer — очень важный класс, который позволяет非接口类型
Создайте прокси Java, Enhancer динамически создает подкласс заданного класса и перехватывает все методы класса прокси.В отличие от динамического прокси JDK, он работает независимо от того, интерфейс это или класс.
Динамический прокси JDK и динамический прокси CGLIB являются реальными объектами.隐藏
за прокси-объектом для достижения代理
Эффект. Отличие от динамического прокси JDK заключается в том, что динамический прокси CGLIB использует Enhancer для создания прокси-объектов, в то время как динамический прокси JDK использует Proxy.newProxyInstance для создания прокси-объектов; другой момент заключается в том, что CGLIB может проксировать большинство классов, в то время как динамический прокси JDK может только прокси для класс, реализующий интерфейс.
Явасский агент
Javassist
— это библиотека классов для редактирования байт-кода в Java; она позволяет программам Java определять новый класс во время выполнения и изменять файл класса при загрузке JVM. Наиболее часто используемые динамические функции:反射
, а отражение также является основой динамического прокси. Причина, по которой мы не упомянули роль отражения в динамическом прокси, заключается в том, что я хочу подробно поговорить об этом позже. Отражение может находить свойства и методы объекта во время выполнения, изменять область действия, вызывать методы по имени метода и т.д. Приложения реального времени не часто создаются с использованием отражения, потому что отражение дорого.Кроме того, есть еще одна функция, столь же мощная, как отражение.Javaassist
.
Давайте сначала продемонстрируем Javaassist на простом примере и то, как Javaassist создает динамические прокси.
Мы по-прежнему используем упомянутые выше UserDao и UserDaoImpl в качестве базовых классов.
Создаем новый класс AssistByteCode, в котором есть метод createByteCode, главное в этом методе — сгенерировать из байт-кода класс реализации UserDaoImpl. Давайте посмотрим на его код ниже
public class AssistByteCode {
public static void createByteCode() throws Exception{
ClassPool classPool = ClassPool.getDefault();
CtClass cc = classPool.makeClass("com.cxuan.proxypattern.UserDaoImpl");
// 设置接口
CtClass ctClass = classPool.get("com.cxuan.proxypattern.UserDao");
cc.setInterfaces(new CtClass[] {ctClass});
// 创建方法
CtMethod saveUser = CtMethod.make("public void saveUser(){}", cc);
saveUser.setBody("System.out.println(\"---- 插入用户 ----\");");
cc.addMethod(saveUser);
Class c = cc.toClass();
cc.writeFile("/Users/mr.l/cxuan-justdoit");
}
}
Поскольку эта статья не является конкретным исследованием Javaassist, мы не будем слишком подробно изучать детали, а сосредоточимся только на некоторых наиболее важных классах этого фреймворка.
ClassPool
: ClassPool является контейнером CtClass иCtClass
Объект — это экземпляр объекта класса, который, как и объект класса, содержит свойства, методы и т. д.
Итак, что в основном делает приведенный выше код? Получите интерфейс, требуемый CtClass, и экземпляр CtClass абстрактного класса через ClassPool, затем добавьте свои собственные атрибуты и методы через экземпляр CtClass и выведите двоичный поток в путь к корневому каталогу текущего проекта через его writeFile. writeFile внутренне используетDataOutputStream
для вывода.
После того, как поток записан, мы открываем этот.class
Файл выглядит так
public class UserDaoImpl implements UserDao {
public void saveUser() {
System.out.println("---- 插入用户 ----");
}
public UserDaoImpl() {
}
}
Вы можете сравнить приведенное выше и обнаружить, что UserDaoImpl считает, что компилятор в основном такой же, за исключением добавления открытого конструктора для нас.
После этого простого примера cxuan покажет вам, как использовать динамический прокси-сервер Javaassist.
Во-первых, давайте создадим фабрику прокси Javaassist, код выглядит следующим образом
public class JavaassistProxyFactory {
public Object getProxy(Class clazz) throws Exception{
// 代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 设置需要创建的子类
proxyFactory.setSuperclass(clazz);
proxyFactory.setHandler((self, thisMethod, proceed, args) -> {
System.out.println("---- 开始拦截 ----");
Object result = proceed.invoke(self, args);
System.out.println("---- 结束拦截 ----");
return result;
});
return proxyFactory.createClass().newInstance();
}
}
Выше мы определили фабрику прокси. В фабрике прокси создается обработчик. При вызове целевого метода Javassist вызовет перехват метода интерфейса MethodHandler для вызова фактического метода выполнения. Вы можете реализовать свою собственную бизнес-логику до и после метод перехвата. ПоследнийproxyFactory.createClass().newInstance()Он заключается в использовании технологии байт-кода для создания окончательного экземпляра подкласса, аналогичного интерфейсу InvocationHandler в JDK.
Метод испытания заключается в следующем.
public static void main(String[] args) throws Exception {
JavaassistProxyFactory proxyFactory = new JavaassistProxyFactory();
UserService userProxy = (UserService) proxyFactory.getProxy(UserService.class);
userProxy.saveUser();
}
Агент ASM
ASM — это среда генерации байт-кода Java, которая может динамически генерировать подклассы или другие прокси-классы в двоичном формате или динамически изменять классы перед их загрузкой в память виртуальной машиной Java.
Ниже мы используем структуру ASM для реализации динамического прокси, динамического прокси, сгенерированного ASM.
Следующий код взят изblog.CSDN.net/светлый ключ 1996/…
public class AsmProxy extends ClassLoader implements Opcodes {
public static void createAsmProxy() throws Exception {
// 目标类类名 字节码中类修饰符以 “/” 分割
String targetServiceName = TargetService.class.getName().replace(".", "/");
// 切面类类名
String aspectServiceName = AspectService.class.getName().replace(".", "/");
// 代理类类名
String proxyServiceName = targetServiceName+"Proxy";
// 创建一个 classWriter 它是继承了ClassVisitor
ClassWriter classWriter = new ClassWriter(0);
// 访问类 指定jdk版本号为1.8, 修饰符为 public,父类是TargetService
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, proxyServiceName, null, targetServiceName, null);
// 访问目标类成员变量 为类添加切面属性 “private TargetService targetService”
classWriter.visitField(ACC_PRIVATE, "targetService", "L" + targetServiceName+";", null, null);
// 访问切面类成员变量 为类添加目标属性 “private AspectService aspectService”
classWriter.visitField(ACC_PRIVATE, "aspectService", "L" + aspectServiceName+";", null, null);
// 访问默认构造方法 TargetServiceProxy()
// 定义函数 修饰符为public 方法名为 <init>, 方法表述符为()V 表示无参数,无返回参数
MethodVisitor initVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
// 从局部变量表取第0个元素 “this”
initVisitor.visitVarInsn(ALOAD, 0);
// 调用super 的构造方法 invokeSpecial在这里的意思是调用父类方法
initVisitor.visitMethodInsn(INVOKESPECIAL, targetServiceName, "<init>", "()V", false);
// 方法返回
initVisitor.visitInsn(RETURN);
// 设置最大栈数量,最大局部变量表数量
initVisitor.visitMaxs(1, 1);
// 访问结束
initVisitor.visitEnd();
// 创建有参构造方法 TargetServiceProxy(TargetService var1, AspectService var2)
// 定义函数 修饰符为public 方法名为 <init>, 方法表述符为(TargetService, AspectService)V 表示无参数,无返回参数
MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(L" + targetServiceName + ";L"+aspectServiceName+";)V", null, null);
// 从局部变量表取第0个元素 “this”压入栈顶
methodVisitor.visitVarInsn(ALOAD, 0);
// this出栈 , 调用super 的构造方法 invokeSpecial在这里的意思是调用父类方法。 <init>的owner是AspectService, 无参无返回类型
methodVisitor.visitMethodInsn(INVOKESPECIAL, targetServiceName, "<init>", "()V", false);
// 从局部变量表取第0个元素 “this”压入栈顶
methodVisitor.visitVarInsn(ALOAD, 0);
// 从局部变量表取第1个元素 “targetService”压入栈顶
methodVisitor.visitVarInsn(ALOAD, 1);
// this 和 targetService 出栈, 调用targetService put 赋值给this.targetService
methodVisitor.visitFieldInsn(PUTFIELD, proxyServiceName, "targetService", "L" + targetServiceName + ";");
// 从局部变量表取第0个元素 “this”压入栈顶
methodVisitor.visitVarInsn(ALOAD, 0);
// 从局部变量表取第2个元素 “aspectService”压入栈顶
methodVisitor.visitVarInsn(ALOAD, 2);
// this 和 aspectService 出栈 将 targetService put 赋值给this.aspectService
methodVisitor.visitFieldInsn(PUTFIELD, proxyServiceName, "aspectService", "L" + aspectServiceName + ";");
// 方法返回
methodVisitor.visitInsn(RETURN);
// 设置最大栈数量,最大局部变量表数量
methodVisitor.visitMaxs(2, 3);
// 方法返回
methodVisitor.visitEnd();
// 创建代理方法 修饰符为public,方法名为 demoQuest
MethodVisitor visitMethod = classWriter.visitMethod(ACC_PUBLIC, "demoQuest", "()I", null, null);
// 从局部变量表取第0个元素 “this”压入栈顶
visitMethod.visitVarInsn(ALOAD, 0);
// this 出栈 将this.aspectService压入栈顶
visitMethod.visitFieldInsn(GETFIELD, proxyServiceName, "aspectService", "L"+aspectServiceName+";");
// 取栈顶元素出栈 也就是targetService 调用其preOperation方法, demoQuest的owner是AspectService, 无参无返回类型
visitMethod.visitMethodInsn(INVOKEVIRTUAL, aspectServiceName,"preOperation", "()V", false);
// 从局部变量表取第0个元素 “this”压入栈顶
visitMethod.visitVarInsn(ALOAD, 0);
// this 出栈, 取this.targetService压入栈顶
visitMethod.visitFieldInsn(GETFIELD, proxyServiceName, "targetService", "L"+targetServiceName+";");
// 取栈顶元素出栈 也就是targetService调用其demoQuest方法, demoQuest的owner是TargetService, 无参无返回类型
visitMethod.visitMethodInsn(INVOKEVIRTUAL, targetServiceName, "demoQuest", "()I", false);
// 方法返回
visitMethod.visitInsn(IRETURN);
// 设置最大栈数量,最大局部变量表数量
visitMethod.visitMaxs(1, 1);
// 方法返回
visitMethod.visitEnd();
// 生成字节码二进制流
byte[] code = classWriter.toByteArray();
// 自定义classloader加载类
Class<?> clazz = (new AsmProxy()).defineClass(TargetService.class.getName() + "Proxy", code, 0, code.length);
// 取其带参数的构造方法
Constructor constructor = clazz.getConstructor(TargetService.class, AspectService.class);
// 使用构造方法实例化对象
Object object = constructor.newInstance(new TargetService(), new AspectService());
// 使用TargetService类型的引用接收这个对象
TargetService targetService;
if (!(object instanceof TargetService)) {
return;
}
targetService = (TargetService)object;
System.out.println("生成代理类的名称: " + targetService.getClass().getName());
// 调用被代理方法
targetService.demoQuest();
// 这里可以不用写, 但是如果想看最后生成的字节码长什么样子,可以写 "ascp-purchase-app/target/classes/"是我的根目录, 阅读者需要将其替换成自己的
String classPath = "/Users/mr.l/cxuan-justdoit/";
String path = classPath + proxyServiceName + ".class";
FileOutputStream fos =
new FileOutputStream(path);
fos.write(code);
fos.close();
}
}
Код для генерации динамического прокси с использованием ASM относительно длинный.Смысл приведенного выше кода в том, чтобы сгенерировать класс TargetServiceProxy, который используется для прокси TargetService.Перед вызовом метода targetService.demoQuest() вызовите аспектService.preOperation();
Тестовый класс может напрямую вызывать метод AsmProxy.createAsmProxy(), что относительно просто.
Ниже приведен целевой класс, который мы генерируем TargetServiceProxy.
На данный момент мы представили четыре способа динамического прокси:динамический прокси-сервер JDK, динамический прокси-сервер CGLIB, динамический прокси-сервер Javaassist, динамический прокси-сервер ASM, а теперь подумайте над вопросом, почему происходит появление динамических агентов? Или каков принцип динамического прокси?
На самом деле, мы уже упоминали об этом выше, да, динамический прокси использует反射
Механизм отражения — это базовая функция языка Java, которая дает программе возможность динамически изменять свойства и методы во время выполнения. Посредством отражения мы можем напрямую манипулировать классами или объектами, например, получать определение определенного класса и получать свойства и методы определенного класса.
Дополнительные сведения об отражении Java см. в этой статье Java Builders.
Следует также отметить, что с точки зрения производительности некоторые люди пришли к выводу, что динамические прокси-серверы Java в десятки раз медленнее, чем CGLIB и Javaassist.На самом деле, в основных версиях JDK динамические прокси-серверы Java могут обеспечить равную производительность.Разрывы порядка величины не распространены. Также в современных JDK улучшена и оптимизирована рефлексия.
В нашем выборе соображения производительности не являются основным направлением.Надежность, ремонтопригодность, усилия по кодированиюодинаково важны.
Я сам переписал шесть PDF-файлов. После того, как WeChat искал «Programmer cxuan» и следил за официальной учетной записью, я ответил cxuan в фоновом режиме и получил все PDF-файлы.Эти PDF-файлы следующие: