JDK动态代理实现原理(用ChatGPT撸代码)
前言
JDK动态代理是java自带的一种运行时生成字节码对目标对象进行代理的机制。
本篇主要介绍下JDK动态代理的实现原理。
示例
先来看一个jdk动态代理的示例(ChatGPT生成):
// 创建一个接口
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
// 该接口的实现
public class NumCalculator implements Calculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
}
// 定义一个 JDK 动态代理处理类,实现 InvocationHandler 接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理类,JDK动态代理,代理接口 Calculator,其实不一定代理Calculator接口,bind方法返回的是Object
* 代理类测试见
*/
public class CalculatorProxy implements InvocationHandler {
/**
* 存储代理接口
*/
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理方法执行前的操作");
Object result = method.invoke(target, args);
System.out.println("代理方法执行后的操作");
return result;
}
}
最后,我们可以在测试代码中使用这个代理对象:
@Test
public void testProxy () {
CalculatorProxy proxy = new CalculatorProxy();
Calculator calculator = (Calculator) proxy.bind(new NumCalculator());
// buildProxy(); // 后面会说此函数的用处
int result = calculator.add(1, 2);
System.out.println("Result: " + result);
}
执行结果如下:
代理方法执行前的操作
代理方法执行后的操作
Result: 3
原理
关键类/接口
1、java.lang.reflect.Proxy
实现动态代理的核心类,提供了创建代理对象的静态方法。
2、java.lang.reflect.InvocationHandler
该接口负责实际处理动态代理对象的方法调用,它是一个接口,用户需要自定义实现类实现该接口来提供代理对象的行为(定义代理行为)。
根据示例看一下职责:
- Proxy:创建代理对象,提供对应静态方法
- InvocationHandler:接口的实现类定义代理对象的行为
- Calculator calculator = (Calculator) proxy.bind(new NumCalculator()); calculator为代理对象引用
- Calculator$proxy:代理类(下文展开)
调用代理类的原理
为什么调用代理对象的方法时,会调用到InvocationHandler实现类的方法,进而调用目标函数呢?
示例中调用如下代码,会执行CalculatorProxy类的invoke方法,再调用到NumCalculator的add方法:
int result = calculator.add(1, 2);
主要看Proxy.newProxyInstance()创建的到底什么:
debug:
可以看到,Proxy.newProxyInstance返回的是一个代理类,另外有一个
h字段,类型为CalculatorProxy。那这个代理代理类具体长什么样子呢?
插播下:我们都知道java的多态的机制:一个变量可以指向该变量对应类的子类实例。
可以通过示例中的测试代码中的 buildProxy(); 进行生成对应的class文件,具体方法实现如下:
public void buildProxy() {
byte[] bytes = ProxyGenerator.generateProxyClass("Calculator$proxy", new Class[]{Calculator.class});
String fileName = System.getProperty("user.dir")+"/target/Calculator$proxy.class";
try {
File file = new File(fileName);
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(bytes);
fileOutputStream.flush();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
执行后,结果生成 Calculator$proxy.class:
idea反编译查看:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import com.mainto.service.proxy.Calculator;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class Calculator$proxy extends Proxy implements Calculator {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m0;
private static Method m3;
public Calculator$proxy(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 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 add(int var1, int var2) throws {
try {
return (Integer)super.h.invoke(this, m4, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
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);
}
}
public final int subtract(int var1, int var2) throws {
try {
return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m4 = Class.forName("com.mainto.service.proxy.Calculator").getMethod("add", Integer.TYPE, Integer.TYPE);
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.mainto.service.proxy.Calculator").getMethod("subtract", Integer.TYPE, Integer.TYPE);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
Calculator$proxy代理类,继承于Proxy,实现了代理的接口。
里面的静态代码块,通过反射生成代理接口中的2个方法引用(另外3个是Object的方法);同时代理类也实现了代理接口中的方法。
当调用代理对象的方法时,根据多态的机制,会调用真实的实现类的方法,即代理类中的方法;方法的实现,则是调用InvocationHandler实现类实现了代理行为的方法,最后调用到目标方法,且完成了代理行为。
字节码生成
动态代理类是如何生成的呢,上面也提到了,使用了Proxy.newProxyInstance静态方法完成。
debug:
最终会调用sun.misc.ProxyGenerator#generateClassFile,通过字节码拼接完成代理类的动态生成。
扩展
JDK动态代理和我们常说的设计模式中的代理模式有什么区别
相同点:
都是代理模式,当客户端访问代理对象时,代理对象会将请求转发给目标对象,同时代理对象可以在请求前后进行一些操作,如校验、日志记录等
不同点:
1、JDK动态代理,属于动态代理,不用编写源代码;代理模式是静态代理
2、JDK动态代理通过反射动态生成的;静态代理需要编码
3、JDK动态代理统一代理行为;静态代理可以随意每个方法的代理行为
为什么JDK代理的对象必须实现接口
通过查看代理类的字节码可以看到代理类:
public final class Calculator$proxy extends Proxy implements Calculator {}
它通过实现Proxy类,传入InvocationHandler的实现,完成代理行为的调用。java是单继承的,所以这里需要通过实现接口的方式完成;另一个点我想可能是能保证实现接口中所有方法,来达成所有方法的代理,也是一种约定,如果是类的话就可实现可不实现了。
代理接口没有实现类的写法
还是上面的例子,可能框架设计时用泛型完成目标功能的实现等,参考:
// 创建一个接口
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
// 定义一个 JDK 动态代理处理类,实现 InvocationHandler 接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理类,JDK动态代理,代理接口 Calculator,其实不一定代理Calculator接口,bind方法返回的是Object
* 代理类测试见
*/
public class CalculatorProxy implements InvocationHandler {
/**
* 存储代理接口
*/
private Class target;
public Object bind(Class target) {
this.target = target;
return Proxy.newProxyInstance(target.getClassLoader(),
new Class[]{target}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理方法执行前的操作");
// Object result = method.invoke(target, args);
System.out.println("代理方法执行后的操作");
return result;
}
}
// 使用时,直接用接口绑定
Calculator calculator = (Calculator) proxy.bind(Calculator.class);