动态代理入门必看

news/2025/3/15 0:58:42/

基本介绍

代理

就是让代理角色帮助真实角色完成一件事情。比如说,我叫我们朋友帮我完成博客,那么这个朋友就是代理,朋友不是我,但是和我完成同样的事,并且还可以对这件事加入他的行为逻辑。
不论是什么代理方法,其根本目的就是为了拓展一个类的方法,也就是想要加入自己的行为逻辑。
当然,我们可以直接修改类的方法,但是一旦类的方法多了起来,就会造成代码的冗余,而且极难进行维护。

静态代理

静态代理相当于是多写了一个代理类,在调用的时候调用的是代理类,在代理类中的处理还是原生的处理逻辑,不过在前后添加上需要添加的代码。
其最根本的特征就是这个代理类需要程序员自己写出来。即在编译阶段就确定了代理对象是谁。

实例说明:

我们编写如下的测试案例:

public class StaticProxyTest {  @Test  public void Test01() {  Vehicle proxyForCar = new ProxyForCar(new Car());  proxyForCar.run("托马斯");  proxyForCar.fly("托马斯");  }  
}

编写接口:

public interface Vehicle {  public void run(String s);  public void fly(String s);  
}

解读:

  • 这里编写接口纯粹是为了符合模板设计模式,没有其他的原因;
  • 这样就可以让代理对象调用原类的方法,不会造成原类的浪费。最大限度的保存原类的功能;
    编写实现类:
public class Car implements Vehicle {  @Override  public void run(String name) {  System.out.println(name + "is running");  }  @Override  public void fly(String name) {  System.out.println(name + "is flying");  }  
}

编写代理类:

public class ProxyForCar implements Vehicle {  Vehicle vehicle;  public ProxyForCar(Vehicle vehicle) {  this.vehicle = vehicle;  }  @Override  public void run(String arg) {  System.out.println("before invoke...");  vehicle.run(arg);  System.out.println("after invoke...");  }  @Override  public void fly(String arg) {  System.out.println("before invoke...");  vehicle.fly(arg);  System.out.println("after invoke...");  }  
}

以上就是一个简单的静态代理的实现。可以发现缺点是显然的,静态代理无法实现切面编程,一旦我们需要代理一个新的接口方法,就要重新写一个方法,甚至重新写一个代理类,这样的开销成本无疑是很大的。
于是我们引入了动态代理。

动态代理

我们这里将主要介绍基于接口实现的 JDK 的动态代理方式
见名知义,动态即我们不需要在自己写代理类了,而是由 JVM 在运行阶段自动的帮我生成一个代理类,在程序结束的时候自动销毁。优势在于:

  • 可延展性高,便于实现切面编程。可以批量的代理类的方法;
  • 极大的简化了代码工程量;
    接下来,我将简单的演示一下动态代理的实现过程,深入分析我们新开一章单独解决。

实例说明

编写测试案例:

public class DynamicProxyTests {  @Test  public void Test01() {  Car car = new Car();  VehicleDynamicProxy vehicleDynamicProxy = new VehicleDynamicProxy(car);  Vehicle proxy = vehicleDynamicProxy.getProxy();  String fly = proxy.fly(10000);  System.out.println("result:\t" + fly);  }  
}

编写接口:

public interface Vehicle {  public String fly(int height);  public void run();  
}

实现类 ship :

public class Ship implements Vehicle {  @Override  public String fly(int height) {  System.out.println("wdnmd");  return "ship 的高度为" + height;  }  @Override  public void run() {  System.out.println("ship 在run");  }  
}

实现类 Car:

public class Car implements Vehicle{  @Override  public String fly(int height) {  System.out.println("wdnmdCar");  return "car 的高度为" + height;  }  @Override  public void run() {  System.out.println("car 在run");  }  
}

动态代理类:

public class VehicleDynamicProxy {  /**  * 被代理对象  */  public Vehicle targetVehicle;  public VehicleDynamicProxy(Vehicle targetVehicle) {  this.targetVehicle = targetVehicle;  }  public Vehicle getProxy() {  //获取类的加载器  ClassLoader classLoader = targetVehicle.getClass().getClassLoader();  //获取类的接口数组  Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();  //调用函数,用来的调用方法  InvocationHandler invocationHandler = new InvocationHandler() {  @Override  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  Object result = null;  try {  System.out.println("前置通知 before");  //调用被代理对象的方法  result = method.invoke(targetVehicle, args);  System.out.println("返回通知 afterReturning");  } catch (Exception e) {  //异常通知 AfterThrowing                    throw new RuntimeException(e);  } finally {  //最终通知 After                }  return result;  }  };  //此时的proxy对象是实现了Vehicle接口,继承了Proxy类的代理对象  Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);  return proxy;  }  
}

解读:

  • JVM 何以动态的创建动态数组?从以上的代码可以看出,我们获得了被代理对象的类加载器和接口数组。显然底层是根据反射动态生成代理类,并且创建实例的;
  • 为了实现调用原对象中的方法,在我们调用代理对象的相应方法的时候,一定会利用 InvocationHandler 中进行调用的转发。也就是说,方法的实际调用不在代理对象中,而由 InvocationHandler 转发到了原对象处理;
  • 这样我们就可以在 invoke 方法中实现自己想要拓展的逻辑,在 Spring 中,就有了前置通知,后置通知等切面编程的思想;

总结

  1. 代理:让代理角色帮助真实角色完成任务,如朋友帮忙完成博客。
  2. 代理方法目的:拓展类的方法,加入自己的行为逻辑。
  3. 静态代理:编写代理类,调用时调用代理类,前后添加需要的代码,需要程序员在编译阶段自行编写代理类。
    • 缺点:无法实现切面编程,需要代理新接口方法时需要重新写方法或代理类。
  4. 动态代理:JVM在运行阶段自动生成代理类,程序结束时销毁。
    • 优势:高可延展性,便于实现切面编程,简化代码工程量。
  5. 动态代理实现过程:
    • 获取被代理对象的类加载器和接口数组。
    • 使用InvocationHandler进行调用转发。
    • 实现自己想要拓展的逻辑。

接下来,我们将从源码的角度,详细的看看 JDK 动态代理的机制。
我们再抛出一个问题,那就是为什么在生成代理对象的时候一定要传入接口数组呢?似乎没有接口,我们也可以通过反射,来转发方法的执行。
【源码解析】JDK 动态代理实现 - yelanyanyu - 博客园 (cnblogs.com)


http://www.ppmy.cn/news/46824.html

相关文章

1.docker-安装及使用

1.安装步骤 Install Docker Engine on CentOS 1. 确定CenOS7及以上版本 cat /etc/redhat-release2.卸载旧版本 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine3.yum安…

HuggingGPT强势来袭,LLM+专家模型,迈向更通用的AI

出品人&#xff1a;Towhee 技术团队 超级组合&#xff1a;HuggingFace ChatGPT HuggingGPT强势来袭。人类仿佛距离真正的AGI又更近了一步。 HuggingGPT是浙江大学与微软亚洲研究院的联手研究&#xff0c;发布之后迅速引发关注&#xff0c;已经开源。 它的使用非常简单&#x…

【密码学复习】第六讲 HASH函数和MAC(三)

H是一个Hash函数 K表示密钥 B表示计算消息摘要时消息分块的字节长度&#xff08;对MD5和SHA-1是512比特,64字节&#xff09; L表示消息摘要按字节计算的长度&#xff08;对MD5是16字节&#xff09; ipad表示0x36重复B次&#xff0c;opad表示0x5c重复B次。 K可以…

有关WPF的一些事

RaisePropertyChanged和OnPropertyChanged的区别 WPF中&#xff0c;数据绑定是实现MVVM模式的关键。在数据绑定的过程中&#xff0c;我们需要在数据发生变化时及时通知UI更新。为了实现这个功能&#xff0c;WPF提供了两个重要的方法&#xff1a;WPFRaisePropertyChanged和OnPr…

逆向-还原代码之(*point)[4]和char *point[4] (Interl 32)

// source code #include <stdio.h> #include <string.h> #include <stdlib.h> /* * char (*point)[4] // 数组指针。 a[3][4] // 先申明二维数组,用它来指向这个二维数组 * char *point[4] // 指针数组。 a[4][5] // 一连串的指针…

flac格式如何转换为mp3,这3个方法超好用

随着音频格式的不断创新和发展&#xff0c;每种格式对应不同的特点。比如像flac格式可以提供无损音质的体验&#xff0c;但它的文件大小却是相对较大&#xff0c;不太适合在普通设备上进行传输和使用。而mp3作为一种流行的音频格式&#xff0c;它的压缩率较高&#xff0c;不但可…

mysql安装(1)

下载软件包 解压tar -zxvf mysql-5.7.28-linux-glibc2.12-x86_64.tar.gz mv mysql-5.7.28-linux-glibc2.12-x86_64 /usr/local/mysql 创建相关目录 mkdir /home/storage/mysql_3306/data logs run tmp 创建用户 groupadd mysql useradd -r -g mysql -s /bin/false mysql 修…

病毒丨3601lpk劫持病毒分析

作者&#xff1a;黑蛋 一、病毒简介 文件名称&#xff1a; 1f3e836b4677a6df2c2d34d3c6413df2c5e448b5bc1d5702f2a96a7f6ca0d7fb 文件类型(Magic)&#xff1a; PE32 executable (GUI) Intel 80386, for MS Windows 文件大小&#xff1a; 52.50KB SHA256&#xff1a; 1f3e836b4…