一、什么是代理
现在老板安排了一个活下来让我来做,我正好有时间,且我也会做,那我直接做了。这也是我们平时较多的场景。那么,现在情况发生了变化,老板把活安排下来后,我这边没时间做,所以我现在去找个人帮我做了,此时对于老板来说,同样的活我还是正常做完了。这个过程,我们就叫做代理。
在上面描述过程中,我们可以发现,代理出现了两个角色:我和帮我干活的人。
在这里,我们将具体干活的人称为执行人,将我称为被代理人。
二、JDK代理
2.1 场景
作为开发者,现在来了一个活:打印今天是几月几号?
2.2 准备工作
2.2.1 准备一个接口
为了后续代码重用,我们准备一个开发者接口,这个接口中有一个打印日期的方法。具体代码如下:
public interface Developer {
void printTodayDate();
}
2.2.2 接口实现
首先,我是一名开发者,我是公子奇,所以我的具体实现如下:
public class DevGongZiQi implements Developer {
@Override
public void printTodayDate() {
System.out.println("公子奇告诉我们:今天的日期是【" + LocalDate.now() + "】");
}
}
2.2.3 不使用代理
我们先来看看,不使用代理的情况下,这个具体的活是由谁来做的。
public class NoProxyMain {
public static void main(String[] args) {
// 实例化一个开发人员
Developer developer = new DevGongZiQi();
System.out.println("看看不使用代理的情况下,具体干活的对象类型: " + developer.getClass());
// 开始干活
developer.printTodayDate();
}
}
我们可以观察上面的输出:
看看不使用代理的情况下,具体干活的对象类型: class com.hz.design.mode.proxy.DevGongZiQi 公子奇告诉我们:今天的日期是【2022-06-14】
根据上面的信息,我们知道具体干活的就是 DevGongZiQi。
2.3 使用代理
现在情况发生了变化,我想让开发人员张三来帮我做这件事,所以我们需要新建一个张三的代理。具体代码如下:
public class DevZhangSan implements InvocationHandler {
Developer developer;
// 将具体执行对象【代理实例】返回
public Object getInstance(Developer developer) {
this.developer = developer;
System.out.println("代理之前,代理的对象类型为:" + developer.getClass().getName());
// 从这里我们也可知道,为什么我们要定义接口,第二个参数需要的必须为接口类型
return Proxy.newProxyInstance(DevZhangSan.class.getClassLoader(), developer.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.developer, args);
}
}
在上面的代码中,此时开发人员张三,不仅仅是一名普通的开发人员,他同时也是一个代理人员。所以,我们将开发者接口作为一个引用放入代理类中,即:开发者接口引用哪个对象,则代理类就执行谁的具体方法。
2.3.1 执行
当使用代理后,我们执行情况如下:
public class DevProxyMain {
public static void main(String[] args) {
Developer developer = (Developer) new DevZhangSan().getInstance(new DevGongZiQi());
System.out.println("使用代理后,具体干活的对象类型为: " + developer.getClass().getName());
developer.printTodayDate();
}
}
结果:代理之前,代理的对象类型为:com.hz.design.mode.proxy.DevGongZiQi 使用代理后,具体干活的对象类型为: com.sun.proxy.$Proxy0 公子奇告诉我们:今天的日期是【2022-06-14】
根据上面结果,我们发现具体干活的对象类型已经发生了变化为 $Proxy0,不过由于他代理的是 DevGongZiQi 的活,所以结果不变,外部看来,还是公子奇做的。
2.4 代理分析
为了更好的观察结果,我们将代理 developer 对应的 class 文件给输出,并反编译查看,JDK的代理后背后帮我们做了哪些工作:
public class DevProxyMain {
public static void main(String[] args) throws IOException {
Developer developer = (Developer) new DevZhangSan().getInstance(new DevGongZiQi());
System.out.println("使用代理后,具体干活的对象类型为: " + developer.getClass().getName());
developer.printTodayDate();
String path = new DevProxyMain().getClass().getResource("").getPath();
// 获取developer的字节码信息
byte[] classBytes = ProxyGenerator.generateProxyClass("$Proxy0", developer.getClass().getInterfaces());
// 将字节码输出到一个 .class 文件
FileOutputStream out = new FileOutputStream(new File(path, "$Proxy0.class"));
out.write(classBytes);
out.close();
}
}
此时,我们可以在 DevProxyMain 类的同级编译目录下找到一个 $Proxy0.class 文件。IDEA自带了反编译,打开后即为代理背后帮我们生成的一个java文件。这里我们列出主要的信息:
// 这里导入相关反射类
....
public final class $Proxy0 extends Proxy implements Developer {
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 {
...
}
// 重点看看这段代码
public final void printTodayDate() throws {
try {
// h:在Proxy类中,为InvocationHandler的一个引用,
// 这也是为什么我们在定义DevZhangSan时,实现InvocationHandler,
// 且在返回代理方法中,传入this的原因。
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 {
...
}
public final int hashCode() throws {
...
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
// 重点看看这一行
m3 = Class.forName("com.hz.design.mode.proxy.Developer").getMethod("printTodayDate");
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());
}
}
}
三、实现自己的代理
在JDK代理中,我们在实现代理对象时,需要实现接口InvocationHandler,那么我们可不可以不使用JDK代理,完全实现自己的代理框架呢?
3.1 参考JDK代理
在前面实现代理的过程及 2.4 节中,我们可以发现,JDK代理主要有以下几个关键类和接口:
Proxy.newProxyInstance(DevZhangSan.class.getClassLoader(), developer.getClass().getInterfaces(), this);
- 一个类加载器 ClassLoader,即 DevZhangSan.class.getClassLoader()
- 一个 InvocationHandler 实现,即 this
- 一个Proxy
3.2 创建自己代理的相关接口和类
既然JDK需要上面三个类和接口,那么我们就参照他,也来健三个:
// 类加载器
public class HZClassLoader extends ClassLoader {
private File baseDir;
public HZClassLoader() {
String basePath = HZClassLoader.class.getResource("").getPath();
this.baseDir = new File(basePath);
}
// 在编译目录下,找到编译成功的 name 类
public Class<?> findClass(String name) {
String className = HZClassLoader.class.getPackage().getName() + "." + name;
if (baseDir != null) {
File classFile = new File(baseDir, name.replaceAll("\.", "/") + ".class");
if (classFile.exists()) {
FileInputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return defineClass(className, out.toByteArray(), 0, out.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return null;
}
}
// 自己的调用处理逻辑接口
public interface HZInvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
// 自己的代理逻辑
public class HZProxy {
HZInvocationHandler h;
public static Object newProxyInstance(HZClassLoader loader, Class<?>[] interfaces, HZInvocationHandler h) throws IllegalArgumentException {
try {
// 1. 生成代理类源码 .java
String src = generateSrc(interfaces[0]);
String baseDir = HZProxy.class.getResource("").getPath();
FileWriter srcFile = new FileWriter(new File(baseDir, "$HZProxy0.java"));
srcFile.write(src);
srcFile.close();
// 平时我们开发中,都是由IDEA帮我们编译的,这里需要我们自己使用代码来完成编译过程
// 2. 编译代理类 .class
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> fileObjects = manager.getJavaFileObjects(new File(baseDir, "$HZProxy0.java"));
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, fileObjects);
task.call();
manager.close();
// 3. 将 class载入JVM
// 4. 返回代理对象
Class<?> proxyClass = loader.findClass("$HZProxy0");
Constructor<?> constructor = proxyClass.getConstructor(HZInvocationHandler.class);
return constructor.newInstance(h);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static final String ln = "\r\n";
// 生成源码,相当于我们上面反编译 $Proxy.class 的过程
private static String generateSrc(Class interfaceType) {
StringBuffer src = new StringBuffer();
src.append("package com.hz.design.mode.proxy;" + ln);
src.append("import java.lang.reflect.Method;" + ln);
src.append("public class $HZProxy0 implements " + interfaceType.getName() + " {" + ln);
src.append("HZInvocationHandler h;" + ln);
src.append("public $HZProxy0(HZInvocationHandler h) {" + ln);
src.append("this.h = h;" + ln);
src.append("}" + ln);
Method[] methods = interfaceType.getDeclaredMethods();
for (Method method : methods) {
src.append("public " + method.getReturnType() + " " + method.getName() + "() {" + ln);
src.append("try {" + ln);
src.append("Method m = " + interfaceType.getName() + ".class.getMethod(""+method.getName()+"", new Class[]{});" + ln);
src.append("this.h.invoke(this, m ,null);" + ln);
src.append("} catch (Throwable e) {e.printStackTrace();}" +ln);
src.append("}" + ln);
}
src.append("}" + ln);
return src.toString();
}
}
3.3 验证自己的代理
这里我们让李四来代理我们自己的:
public class DevLiSi implements HZInvocationHandler {
private Developer developer;
public Object getInstance(Developer developer) {
this.developer = developer;
System.out.println("实现代理前的类型: " + this.developer.getClass().getName());
return HZProxy.newProxyInstance(new HZClassLoader(), developer.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.writeCode, args);
}
}
public class Main {
public static void main(String[] args) {
Developer proxyLiSi = (Developer) new DevLiSi().getInstance(new DevGongZiQi());
System.out.println("生成的代理类: " + proxyLiSi.getClass().getName());
proxyLiSi.printTodayDate();
}
}