细说java动态代理及使用场景

news/2025/2/12 18:09:53/

一、定义

Java代理模式是一种结构型设计模式,它允许通过创建一个代理对象来间接访问另一个对象,从而控制对原始对象的访问。

1.1 作用

1、在访问原始对象时增加额外功能,如访问前或访问后添加一些额外的行为。
2、控制对原始对象的访问。

Java代理模式包括两个主要组件:代理类和实际的对象。当客户端调用代理对象的方法时,代理对象会将请求转发到实际对象,并在必要时添加额外的功能。这些额外的功能可以是日志记录、安全性检查、缓存等等。

1.2 分类

Java代理模式可以分为静态代理和动态代理两种类型:

  • 静态代理:需要手动编写代理类,代理对象和原始对象都需要实现相同的接口。
  • 动态代理:使用Java反射机制自动生成代理类,在运行时绑定原始对象和代理对象,无需手动编写代理类,但原始对象必须实现接口。
    Java代理模式在开发中广泛应用,它提供了更高级别的抽象,使得代码更加清晰、易于维护和调试

下面我们分别来介绍这两种代理方式。

二、静态代理

这个比较简单,我们直接上图上代码,这种代理方式在平时开发过程中也不是太多。
在这里插入图片描述
一个接口,两个实现类,一个真实现,一个假实现,代理类持有实现类,通过实现类来做操作。
举例:买股票。
在这里插入图片描述
代码如下

// 定义一个交易接口
interface ITransaction {void buy(String stockName, int quantity);void sell(String stockName, int quantity);
}// 实现交易接口的真实交易类
class RealTransaction implements ITransaction {@Overridepublic void buy(String stockName, int quantity) {System.out.println("买入 " + quantity + " 股 " + stockName + " 成功");}@Overridepublic void sell(String stockName, int quantity) {System.out.println("卖出 " + quantity + " 股 " + stockName + " 成功");}
}// 交易静态代理类
class TransactionProxy implements ITransaction {private ITransaction realTransaction;public TransactionProxy(ITransaction realTransaction) {this.realTransaction = realTransaction;}@Overridepublic void buy(String stockName, int quantity) {// 在真实交易前做一些操作System.out.println("交易开始...");// 调用真实交易类的买入方法realTransaction.buy(stockName, quantity);// 在真实交易后做一些操作System.out.println("交易结束。");}@Overridepublic void sell(String stockName, int quantity) {// 在真实交易前做一些操作System.out.println("交易开始...");// 调用真实交易类的卖出方法realTransaction.sell(stockName, quantity);// 在真实交易后做一些操作System.out.println("交易结束。");}
}// 测试类
public class TransactionTest {public static void main(String[] args) {// 创建一个真实交易类对象RealTransaction realTransaction = new RealTransaction();// 创建交易静态代理类对象,传入真实交易类对象TransactionProxy transactionProxy = new TransactionProxy(realTransaction);// 调用交易静态代理类的买入方法transactionProxy.buy("AAPL", 100);transactionProxy.sell("AAPL", 100);}
}

三、动态代理

动态代理更加灵活,代理类在程序运行时创建。
动态代理在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。如:Spring AOP、retrofit、注解(如wmrouter)等。

我们常见的动态代理使用方式有三种:

  1. 基于 JDK 实现动态代理 (只能代理实现了接口的类)
  2. 基于CGlib 动态代理模式 (基于ASM的字节码生成库)
  3. 基于 Aspectj 实现动态代理

我们飞别来介绍一下

3.1 基于 JDK 实现

即JDK 动态代理机制,JDK动态代理是一种实现在Java中实现AOP编程的方式。它可以在jvm运行时创建一个代理对象(动态地创建某个接口的实例),用于替换原始对象并截取其方法调用。
先上一张图
在这里插入图片描述

3.1.1我们先说下使用步骤

动态代理主要分为以下两个部分:

代理对象的静态生成过程 (将定义的接口以及 InvocationHandler 实例传递给 Proxy.newProxyInstance() 方法)
-> —
对代理对象方法的拦截和重构过程(代理对象会进入 InvocationHandler 的 invoke() 方法,从而可以通过反射机制去调用具体的真实对象)

  • 第一步:实现InvocationHandler接口创建自己的调用处理器
package com.test.daili;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/*** @param <T>*/
public class BrokerInvocationHandler<T> implements InvocationHandler {/*** 持有的被代理对象*/T target;public BrokerInvocationHandler(T target) {this.target = target;}/*** proxy:代表动态代理对象* method:代表正在执行的方法* args:代表调用目标方法时传入的实参*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理开始执行" +method.getName() + "方法");//代理过程中插入其他操作// TODO: 2023/5/11 计算手续费Object result = method.invoke(target, args);// TODO: 2023/5/11 数据库操作return result;}
}
  • 第二步:新建一个交易接口及其实现类
public interface ITrade {void trade();
}public class StockMan implements ITrade {private String codename;private int codenum;public StockMan(String codename) {this.codename = codename;}public StockMan(String codename, int codenum) {this.codename = codename;this.codenum = codenum;}@Overridepublic void trade() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(codename + ", num=" + codenum + " 交易");}
}
  • 第三步:使用newInstanceProxy 完成代理对象的创建

public class ProxyTest {public static void main(String[] args) {//        创建一个被代理的实例对象ITrade stockMan = new StockMan("中国平安", 1000);//创建一个与代理对象相关联的InvocationHandlerInvocationHandler stuHandler = new BrokerInvocationHandler<ITrade>(stockMan);//创建一个代理对象 ,代理对象的每个执行方法都会替换执行 Invocation中的 invoke方法ITrade stockProxy = (ITrade) Proxy.newProxyInstance(StockMan.class.getClassLoader(),new Class<?>[]{ITrade.class}, stuHandler);//代理执行交易方法stockProxy.trade();}
}

3.1.2 jdk动态代理原理

JDK动态代理主要是基于反射,使用反射解析目标对象的属性、方法等,根据解析的内容生成proxy.class,大白话就是我们可以在运行时生成一个代理对象,并将所有方法调用转发给我们指定的处理器。
在这里插入图片描述
继续看图,别忘记了
在这里插入图片描述
在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心,其中Proxy用于创建实现了一组给定接口的代理类,InvocationHandler则负责处理代理类的方法调用。

InvocationHandler 接口

public interface InvocationHandler {proxy - 代理的真实对象method - 指的是我们所要调用真实对象的某个方法的Method对象args - 指的是调用真实对象某个方法时接受的参数public Object invoke(Object realIproxy, Method method, Object[] args)throws Throwable;
}每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个InvocationHandler

Proxy

// 1、判断是否有创建代理类的权限// 2、获取代理类 class 对象
Class<?> cl = getProxyClass0(loader, intfs);// 3、获得代理类构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);// 4、反射创建实例// 指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy);// 关联于指定类装载器和一组接口的动态代理类的类对象
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces); // 判断指定类对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl);

真正的代理类对象是com.sun.proxy.$Proxy0,我们定义InvocationHandler类是用于添加对代理类的功能扩展!而
$Proxy0类继承java.lang.reflect.Proxy类 并实现ITrade接口 ,因此它的类声明方式如下:

public class  $Proxy0 extends Proxy  implements ITrade 

具体来看下生成过程

loader :类加载器,用于加载代理对象。
interfaces : 被代理类实现的一些接口;
h : 实现了 InvocationHandler 接口的对象;
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){//所有被实现的业务接口final Class<?> caller = System.getSecurityManager() == null? null: Reflection.getCallerClass();// 2、通过反射类中的Constructor获取其所有构造方法Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);// 3、用构造方法创建代理类com.sun.proxy.$ProxyX的实例,并传入InvocationHandler参数return cons.newInstance(new Object[]{h});
}/*** 核心生成字节码的方法*/
private static Constructor<?> getProxyConstructor(Class<?> caller,ClassLoader loader,Class<?>... interfaces){// optimization for single interfaceif (interfaces.length == 1) {...return proxyCache.sub(intf).computeIfAbsent(loader,(ld, clv) -> new ProxyBuilder(ld, clv.key()).build());} else {// interfaces clonedfinal Class<?>[] intfsArray = interfaces.clone();...return proxyCache.sub(intfs).computeIfAbsent(loader,(ld, clv) -> new ProxyBuilder(ld, clv.key()).build());}}在builder方法内部,有这么一句/** Generate the specified proxy class.*/byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);

为了更清楚的看到调用过程,我们仿ProxyGenerator.generateProxyClass方法的方式生成其class字节码

//真实对象try {Class proxyGenerator = Class.forName("java.lang.reflect.ProxyGenerator");Method method = proxyGenerator.getDeclaredMethod("generateProxyClass", String.class, Class[].class);method.setAccessible(true);//生成代理类byte[] proxyClassFile = (byte[]) method.invoke(null, "$Proxy0", stockMan.getClass().getInterfaces());//保存在本地文件中try (FileOutputStream fis = new FileOutputStream(new File("./$Proxy0.class"))){fis.write(proxyClassFile);} catch (Exception e) {e.printStackTrace();}} catch (Exception e) {throw new RuntimeException(e);}

生成的代码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//import com.test.daili.ITrade;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;/*** Proxy会为我们生成一个实现了ITrade接口并继承了Proxy的业务代理类$Proxy0* 在进行方法调用时,其实是调用了InvocationHandler的 invoke方法,*  如下: super.h.invoke(this, m3, (Object[])null);*/
public final class $Proxy0 extends Proxy implements ITrade {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}/*** 方法调用*/public final void trade() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.test.daili.ITrade").getMethod("trade");m2 = Class.forName("java.lang.Object").getMethod("toString");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

3.2 CGLIB 动态代理

CGLIB 动态代理是基于目标对象创建子类的方式实现的,它可以在运行时动态修改目标对象的字节码,从而达到代理的目的,可以代理没有任何接口的类( JDK 动态代理必须要接口)。

相比于静态代理和 JDK 动态代理,CGLIB 的性能更优秀,因为其直接操作字节码,避免了反射机制的调用,使得代理方法的调用速度更快。但是,CGLIB 也有缺点,就是在创建代理类时需要消耗更多的时间和内存。

由于一些原因,这个大家可自行学习:
https://github.com/cglib/cglib

四、使用场景

动态代理的使用场景比较多,常见的各种开源框架如:Spring AOP、retrofit(create() 方法)、注解(如wmrouter)

除了各种框架,我们在项目中也会使用到,如 hook toast报错,webview client hook等

demo


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

相关文章

ARM汇编 C语言数据存储 堆和栈的区别

ARM汇编 ARM汇编是一种用于编写针对ARM架构的汇编语言。它是ARM处理器的底层指令集的人类可读表示形式&#xff0c;用于编写底层的系统级代码或优化特定的程序。 ARM汇编语言使用助记符&#xff08;mnemonic&#xff09;来表示不同的指令操作&#xff0c;例如"ADD&quo…

apk 作为资源提供 aar 的过程

1&#xff1a;参考&#xff1a;Android将APK项目封装为SDK(AAR) https://blog.csdn.net/weixin_51522235/article/details/128216091 四大点&#xff1a;1: apply plugin:com.android.library 2:去掉&#xff1a;applicationId 3:去掉&#xff1a;applicationVariants.all…

keycloak异常关闭报错username ‘admin‘ already added时卡死无法重启的问题处理

问题现象 使用docker部署keycloak服务&#xff0c;使用docker-compose进行配置管理&#xff0c;配置如下&#xff1a; keycloak:image: jboss/keycloak:16.1.0 container_name: keycloakcommand:[-b,0.0.0.0,-Dkeycloak.migration.actionimport,-Dkeycloak.migration.provider…

深入理解 Linux 内核

Linux 内核系列文章 Linux 内核设计与实现 深入理解 Linux 内核 深入理解 Linux 内核&#xff08;二&#xff09; Linux 设备驱动程序 Linux设备驱动开发详解 文章目录 Linux 内核系列文章前言一、绪论二、内存寻址1、内存地址2、硬件中的分段&#xff08;1&#xff09;段选择符…

streamlit简介和使用教程2

文章目录 显示文本显示图像、视频音频进度和状态侧边栏和容器侧边栏容器显示图表显示文本 #显示文本 st.write("Hello,lets learn how to build a streamlit app together")st.title():用于添加应用程序的标题st.header():用于设置节的标题st.subheader():用于设…

深入分析实战可重入读写锁ReentrantReadWriteLock

文章目录 前言加锁规则同步原理源码解析实战演示 前言 前面我们学习了可重入锁ReentrantLock&#xff0c;可重入锁是一个排他锁&#xff0c;只要不是当前线程访问加锁资源都不能够进入&#xff0c;只能等待锁的释放。当然&#xff0c;这种加锁方式也有一定的适用场景。但是&am…

nginx压测记录

nginx压测记录 1 概述2 原理3 环境3.1 设备与部署3.2 nginx配置/服务器配置 4 netty服务5 步骤6 结果7 写在最后 1 概述 都说nginx的负载均衡能力很强&#xff0c;最近出于好奇对nginx的实际并发能力进行了简单的测试&#xff0c;主要测试了TCP/IP层的长链接负载均衡 2 原理 …

【libdatachannel】1 :cmake+vs2022 构建

libdatachannel libdatachannel 是基于c++17实现的cmake 链接openssl 可以参考【libcurl 】win32 构建 Release版本 修改cmakelist 链接openssl1.1.*构建 OpenSSL 找不到 Selecting Windows SDK version 10.0.22000.0 to target Windows 10.0.22621. The CXX compiler identifi…