一、定义
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 {
@Override
public void buy(String stockName, int quantity) {
System.out.println("买入 " + quantity + " 股 " + stockName + " 成功");
}
@Override
public 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;
}
@Override
public void buy(String stockName, int quantity) {
// 在真实交易前做一些操作
System.out.println("交易开始...");
// 调用真实交易类的买入方法
realTransaction.buy(stockName, quantity);
// 在真实交易后做一些操作
System.out.println("交易结束。");
}
@Override
public 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)等。
我们常见的动态代理使用方式有三种:
- 基于 JDK 实现动态代理 (只能代理实现了接口的类)
- 基于CGlib 动态代理模式 (基于ASM的字节码生成库)
- 基于 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:代表调用目标方法时传入的实参
*/
@Override
public 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;
}
@Override
public 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);
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler 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 interface
if (interfaces.length == 1) {
...
return proxyCache.sub(intf).computeIfAbsent(
loader,
(ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
);
} else {
// interfaces cloned
final 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