会话1: 什么是动态代理?
- 👧 什么是代理啊?
- 👨来来来,听我细细来说
代理这个词在不同的上下文中有不同的含义,主要可以归纳为以下几类解释:
- 计算机网络中的代理服务器(Proxy Server): 代理服务器是在网络中充当中间人的系统,它位于客户端和目标服务器之间。它的主要作用包括提供访问控制、数据缓存、匿名浏览、安全过滤等功能。代理服务器可以在开放系统互联(OSI)模型的会话层工作,起到防火墙的作用,帮助用户或网络访问Internet资源,同时隐藏用户的真实身份或提高访问速度。
- 编程中的代理(Delegate): 在编程语言如C#中,代理(Delegate)是一种引用类型,类似于C++中的函数指针,但更加强大和类型安全。它允许开发者将方法作为参数传递给其他方法,或者将方法存储在变量中供后续调用。委托是面向对象的方式实现的,可以理解为封装了方法调用的一种类型,使得方法像对象一样可以被操作和传递。
- 法律意义上的代理: 法律上的代理是指一种法律安排,其中一方(代理人)在另一方(被代理人)的授权下,代表被代理人在法律规定的范围内与第三方进行民事活动。这种行为的法律后果直接由被代理人承担。代理可以分为直接代理(显名代理)和间接代理(隐名代理),并且依据代理权产生的原因不同,还可以分为委托代理、法定代理和指定代理等。代理涉及到至少三方当事人:被代理人、代理人和第三人。
总结来说,代理既可以指技术领域中用于中转、控制或代表客户端进行网络请求的服务器,也可以是编程语言中的一个概念,用以实现方法的动态调用和事件处理,还可能是法律框架内代表他人行事并承担相应法律后果的一种法律行为。
下面几种都可以认为是代理
卖周杰伦演唱会门票黄牛,老板的小秘书
本来通过官方购票渠道购买,但是由于购票困难,通过黄牛帮我们做个代理帮我去买票,让他帮我们去买票,他就是我们的代理,他们也是买票平台的代理
还有技术上的 nginx 反向代理转发请求
会话2: 动态代理与静态代理
- 👧 那什么是动态代理啊?有动态代理那是不是有静态代理
- 👨 哇厉害厉害!都学会举一反三了。
简单来说静态代理能够代表某一个或者某一类去做操作,而动态代理则是更加灵活,能够动态的代表所有,能够操作这个代理来间接操作任何他所代表的。
静态代理在不同的领域有着不同的含义,主要可以分为以下两类:
- 计算机网络领域的静态代理IP: 这是指一种个人代理服务器服务,其中提供的IP地址是固定不变的,专为单个用户分配和使用,不与其他用户共享。静态代理IP地址的稳定性和专享性使其在需要高度匿名性、稳定连接和特定IP信誉的场景中尤为有用,例如社交媒体账户管理、电子商务运营、数据抓取等。由于这些特性,静态代理往往价格较高,但提供了更好的控制度和安全性。
- 软件工程中的静态代理设计模式: 在软件开发中,静态代理是一种设计模式,指的是在程序编译期间就已经确定的代理类。这意味着代理类的字节码文件在程序运行前就已经存在,代理类和目标类(即被代理的类)之间的关系是预先定义好的。静态代理通过扩展目标类的功能,可以在不修改目标类代码的情况下,为对象增加额外的功能,比如日志记录、权限控制、性能监控等。然而,这种方式不够灵活,如果目标类的接口发生变化,代理类也需要相应地调整,增加了维护成本。
综上所述,无论是网络领域的静态代理IP,还是软件工程中的静态代理设计模式,它们的核心特点都是提供一个固定的、预先定义好的中间层,用于控制访问、扩展功能或保护原有的系统或资源。
动态代理就像下面一样
想象一下,你经营着一家快递公司,每天有很多包裹需要寄送。为了保证服务质量,你需要对每个包裹的寄送过程进行跟踪和管理,比如记录发送时间、检查包裹内容是否合规、以及确认送达时间等。但是,如果直接让每个快递员做这些事情,会很麻烦,而且容易忘记或者出错。
这时候,你可以雇佣一位特别的助手——我们叫他“代理小哥”。这位代理小哥不直接送包裹,他的工作是在真正的快递员投递前后,悄悄加上一些额外的服务,比如自动记录时间、检查包裹,但不让快递员感觉到负担。
动态代理就像是这位“代理小哥”,不过更神奇的是,这位小哥可以根据不同的包裹和快递员,灵活变化自己的服务内容,而且他是根据每天的实际需求即时请来的,不需要提前安排好每个人对应的助手是谁。这样,你的快递公司就能在不影响快递员正常工作的前提下,高效地完成额外的管理任务了。
简单来说,动态代理就是在软件世界里,一种灵活又智能的方式,帮助我们在不改动原有程序核心功能的情况下,给它增加新的功能,比如安全检查、数据记录等,让程序运行得更加顺畅和安全。
会话3: show me code with java
- 👧 说的那么简单,show me code with java
- 👨 okok easy easy
静态代理 那我就举个黄牛卖票的例子吧
package org.fpp.proxy;/*** @author bigbird-0101* @date 2024-06-20 22:31*/
public class StaticProxy {public static void main(String[] args) {You you=new You();you.maiPiao();}/*** 你自己*/public static class You{public void maiPiao(){//找到黄牛HuangNiu huangNiu=new HuangNiu();//向黄牛买票huangNiu.maiPiao();}}public static class HuangNiu{private ZhouJieLunGangFang zhouJieLunGangFang;public HuangNiu(){this.zhouJieLunGangFang = new ZhouJieLunGangFang();}public void maiPiao(){zhouJieLunGangFang.maiPiao();}}public static class ZhouJieLunGangFang {public void maiPiao(){System.out.println("卖一张周杰伦演唱会门票");}}
}
可以看到已经买到的一张门票了
- 👧 哇哦,哥哥好厉害哦,但是人家还想去奶茶刘若英的演唱会,咋办?
- 👨 这还不简单 看我的操作,嘿嘿
package org.fpp.proxy;/*** @author bigbird-0101* @date 2024-06-20 22:31*/
public class StaticProxy2 {public static void main(String[] args) {You you=new You();you.maiPiao();}/*** 你自己*/public static class You{public void maiPiao(){//找到黄牛HuangNiu huangNiu=new HuangNiu();//向黄牛买票huangNiu.maiPiao();}}public static class HuangNiu{private LiuLuoYing liuLuoYing;public HuangNiu(){this.liuLuoYing = new LiuLuoYing();}public void maiPiao(){liuLuoYing.maiPiao();}}public static class LiuLuoYing {public void maiPiao(){System.out.println("卖一张刘若英演唱会门票");}}
}
会话4: 静态代理代码 简单形式
- 👧 哇哦,哥哥好厉害哦,但是人家还想去梁静茹的演唱会,咋办?
- 👨 我特么。。。 这个代码搞不定了,容我想一会
你看我把卖票做成一个通用的接口了,黄牛可以无缝对接任何买票的,你可以向他买到任何明星的演唱会门票。
package org.fpp.proxy;/*** @author bigbird-0101* @date 2024-06-20 22:31*/
public class StaticProxy3 {public static void main(String[] args) {You you=new You();you.maiPiao();}/*** 你自己*/public static class You{public void maiPiao(){//找到黄牛HuangNiu huangNiu=new HuangNiu(new LiangjingRu());//向黄牛买票huangNiu.maiPiao();}}public static class HuangNiu{private MaiPiao maiPiao;public HuangNiu(MaiPiao maiPiao){this.maiPiao = maiPiao;}public void maiPiao(){maiPiao.maiPiao();}}public static class LiangjingRu implements MaiPiao{@Overridepublic void maiPiao(){System.out.println("卖一张梁静茹演唱会门票");}}public interface MaiPiao{void maiPiao();}
}
会话5: 动态代理代码 深入理解,自己实现一个动态代理
- 👧 哇哦,哥哥好厉害哦,好厉害好厉害
- 👨 哈哈哈哈哈
- 👧 哥哥,这个黄牛这么厉害,我可不可以找他买小米手机呢?小米手机好难抢啊
- 👨 额。。。这个这个。。。他只能卖演唱会门票啊,找他买手机有点难 我想想。
初步思想
public class DynamicProxy {public static class WanNengHuangNiu{}public static class MaiPiaoProxy implements MaiPiao {private MaiPiao liangjingRu;public MaiPiaoProxy() {liangjingRu = new MaiPiao() {@Overridepublic void maiPiao() {}};}@Overridepublic void maiPiao(){liangjingRu.maiPiao();}}public interface MaiPiao{void maiPiao();}public static class MaiShouJiProxy implements MaiShouJi {private MaiShouJi maiShouJi;public MaiShouJiProxy() {maiShouJi = new MaiShouJi() {@Overridepublic void maiShouJi() {}};}@Overridepublic void maiShouJi() {maiShouJi.maiShouJi();}}public interface MaiShouJi{void maiShouJi();}
}
怎么实现这个万能代对象?可以看到普通的下面的买票的代理 和 卖手机的代理实现过程。我们找一下相同点。
- 实现这个代理 我们都实现了一个接口
- 并在这个代理内部构建一个接口的实现。 作为类的属性变量,并且代理类也实现这个接口,并调用这个被代理对象
我怎么基于 上面两个相同点来写一个万能的?既然要求是万能的?说明这个类也是一直在变化的,我能不能动态的生成这个类呢?在万能代这个对象当中呢?
来我们尝试一下(前提是被代理的一个接口)
package org.fpp.proxy;/*** @author bigbird-0101* @date 2024-06-20 22:52*/
public class DynamicProxy2 {public static void main(String[] args) {WanNengHuangNiu wanNengHuangNiu = new WanNengHuangNiu(MaiPiao.class);}public static class WanNengHuangNiu{public WanNengHuangNiu(Class<?> clazz) {String name = clazz.getName();buildClassString(name);}//拼接要生成的代码 生成的动态代理类以 被代理接口+Proxy命名规则来命名public String buildClassString(String interfaceName){StringBuilder stringBuilder=new StringBuilder();stringBuilder.append("public class ").append(interfaceName).append("Proxy").append(" implements ").append(interfaceName).append("{").append("\n");stringBuilder.append("}");System.out.println(stringBuilder);return stringBuilder.toString();}}public static class MaiPiaoProxy implements MaiPiao {private MaiPiao liangjingRu;public MaiPiaoProxy() {liangjingRu = new MaiPiao() {@Overridepublic void maiPiao() {}};}@Overridepublic void maiPiao(){liangjingRu.maiPiao();}}public interface MaiPiao{void maiPiao();}public static class MaiShouJiProxy implements MaiShouJi {private MaiShouJi maiShouJi;public MaiShouJiProxy() {maiShouJi = new MaiShouJi() {@Overridepublic void maiShouJi() {}};}@Overridepublic void maiShouJi() {maiShouJi.maiShouJi();}}public interface MaiShouJi{void maiShouJi();}
}
查看程序的输出,可以看到我们已生成买票的代理类代码,但是我们没有补充实现买票的接口这个时候我们继续生成代码。
进阶实现
改造后的代码
我们构造了被代理接口的实现
public static class WanNengHuangNiu{public WanNengHuangNiu(Class<?> clazz) {buildClassString(clazz);}public String buildClassString(Class<?> clazz){String interfaceName = clazz.getName();StringBuilder stringBuilder=new StringBuilder();stringBuilder.append("public class ").append(interfaceName).append("Proxy").append(" implements ").append(interfaceName).append("{").append("\n");//拼接被代理接口的作为代理类的属性stringBuilder.append(" private ").append(interfaceName).append(" proxyobj1;\n");//拼接代理类的构造函数stringBuilder.append(" public ").append(interfaceName).append("Proxy() {").append("\n");stringBuilder.append(" this.proxyobj1 = new ").append(interfaceName).append("(){").append("\n");//拼接实现的方法Method[] methods = clazz.getMethods();for (Method method:methods){stringBuilder.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(){").append("\n");stringBuilder.append(" }\n");}stringBuilder.append(" };\n");stringBuilder.append(" }\n");stringBuilder.append("}\n");System.out.println(stringBuilder);return stringBuilder.toString();}}
**查看输出 **
可以看到已经达到我们预期的结果,现在我们再让 DynamicProxy2$MaiPiaoProxy 来实现接口的方法,并调用我们的内部接口实现类
public static class WanNengHuangNiu{public WanNengHuangNiu(Class<?> clazz) {buildClassString(clazz);}public String buildClassString(Class<?> clazz){String interfaceName = clazz.getName();StringBuilder stringBuilder=new StringBuilder();stringBuilder.append("public class ").append(interfaceName).append("Proxy").append(" implements ").append(interfaceName).append("{").append("\n");//拼接被代理接口的作为代理类的属性stringBuilder.append(" private ").append(interfaceName).append(" proxyobj1;\n");//拼接代理类的构造函数stringBuilder.append(" public ").append(interfaceName).append("Proxy() {").append("\n");stringBuilder.append(" this.proxyobj1 = new ").append(interfaceName).append("(){").append("\n");//拼接实现的方法Method[] methods = clazz.getMethods();for (Method method:methods){stringBuilder.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(){").append("\n");stringBuilder.append(" }\n");}stringBuilder.append(" };\n");stringBuilder.append(" }\n");//拼接代理类 实现接口方法并调用被代理类的方法for (Method method:methods){stringBuilder.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(){").append("\n");stringBuilder.append(" proxyobj1.").append(method.getName()).append("();").append("\n");stringBuilder.append(" }\n");}stringBuilder.append("}\n");System.out.println(stringBuilder);return stringBuilder.toString();}}
查看结果 **
已经按我们的预期输出
可以看到我们已经生成了很完整的代码?那么问题来了我们怎么让我们生成的代码跑起来呢?
通过类加载器加载这个我们已经生成的代码,但是我们的java类加载器并没有那种直接加载string代码的方法。那咋办 我们仔细阅读classloader的源码,发现有一个 defineClass,这个方法试讲字节转class对象这个是我们想要的,我们把我们生成的代码转成byte,然后通过这个方法再次转成class,转成class后我们就能构造对象了**。但是由于这个方法是 protected的 所以只能被他的子类调用所以这里我们需要自定义类加载器。
完善实现
自定义类加载器
public static class MyClassLoader extends ClassLoader{public Class<?> loadStringCode(String className,String code){byte[] bytes = code.getBytes(StandardCharsets.UTF_8);Class<?> clazz = defineClass(className,bytes,0,bytes.length);return clazz;}}
查看输出
发现 defineclass 的 byte[] 必须是符合java字节码的格式,明显 字符串.getBytes明显不是字节码的标准。所以提示这个,那怎么解决这个问题呢?
利用java的自带的编译工具将string 编译成字节码,请要注意$的大坑和import
public static class WanNengHuangNiu{public WanNengHuangNiu(Class<?> clazz) {String s = buildClassString(clazz);String className = clazz.getSimpleName().replaceAll("\\$","") + "Proxy";byte[] bytes = compileCodeFromString(className, s);//将文件MyClassLoader myClassLoader=new MyClassLoader();myClassLoader.loadStringCode(className,bytes);Class<?> aClass;try {aClass = myClassLoader.loadClass(className);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}System.out.println(aClass);}private byte[] compileCodeFromString(String className,String code) {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();if (compiler == null) {throw new IllegalStateException("JDK required to compile at runtime");}DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();final Map<String, byte[]> inMemoryClasses = new HashMap<>();StandardJavaFileManager stdFileMgr = compiler.getStandardFileManager(null, null, null);MemoryFileManager fileManager = new MemoryFileManager(inMemoryClasses,stdFileMgr);JavaFileObject sourceFile = new StringJavaSource(className, code);JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, Collections.singletonList(sourceFile));boolean success = task.call();if (!success) {throw new RuntimeException("Compilation failed: " + diagnostics.getDiagnostics());}return inMemoryClasses.get(className);}static private class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {private final Map<String, byte[]> map;MemoryFileManager(Map<String, byte[]> map, JavaFileManager delegate) {super(delegate);this.map = map;}@Overridepublic JavaFileObject getJavaFileForOutput(Location location, String className,JavaFileObject.Kind kind, FileObject sibling) throws IOException {if (location == StandardLocation.CLASS_OUTPUT && kind == JavaFileObject.Kind.CLASS) {return createInMemoryClassFile(className);} else {return super.getJavaFileForOutput(location, className, kind, sibling);}}private JavaFileObject createInMemoryClassFile(String className) {URI uri = URI.create("memory:///" + className.replace('.', '/') + ".class");return new SimpleJavaFileObject(uri, JavaFileObject.Kind.CLASS) {@Overridepublic OutputStream openOutputStream() {return new ByteArrayOutputStream() {@Overridepublic void close() throws IOException {super.close();map.put(className, toByteArray());}};}};}}static class StringJavaSource extends SimpleJavaFileObject {final String code;StringJavaSource(String name, String code) {super(URI.create("string:///" + name + Kind.SOURCE.extension), Kind.SOURCE);this.code = code;}@Overridepublic CharSequence getCharContent(boolean ignoreEncodingErrors) {return code;}}public String buildClassString(Class<?> clazz){String interfaceName = clazz.getName().replaceAll("\\$",".");StringBuilder stringBuilder=new StringBuilder();stringBuilder.append("import ").append(interfaceName).append(";\n");stringBuilder.append("public class ").append(clazz.getSimpleName()).append("Proxy").append(" implements ").append(interfaceName).append("{").append("\n");//拼接被代理接口的作为代理类的属性stringBuilder.append(" private ").append(interfaceName).append(" proxyobj1;\n");//拼接代理类的构造函数stringBuilder.append(" public ").append(clazz.getSimpleName()).append("Proxy() {").append("\n");stringBuilder.append(" this.proxyobj1 = new ").append(interfaceName).append("(){").append("\n");//拼接实现的方法Method[] methods = clazz.getMethods();for (Method method:methods){stringBuilder.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(){").append("\n");stringBuilder.append(" }\n");}stringBuilder.append(" };\n");stringBuilder.append(" }\n");//拼接代理类 实现接口方法并调用被代理类的方法for (Method method:methods){stringBuilder.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(){").append("\n");stringBuilder.append(" proxyobj1.").append(method.getName()).append("();").append("\n");stringBuilder.append(" }\n");}stringBuilder.append("}\n");System.out.println(stringBuilder);return stringBuilder.toString();}}
输出结果
可以看到已经成功编译
这个是我生成的代码 我遇到了一个有意思的问题 这段代码 java帮我编译生成了两个class
import org.fpp.proxy.DynamicProxy2;
import org.fpp.proxy.DynamicProxy2.MaiPiao;
public class MaiPiaoProxy implements MaiPiao{private MaiPiao proxyobj1;public MaiPiaoProxy() {this.proxyobj1 = new MaiPiao(){public void maiPiao(){System.out.println(" 我是代理哈哈哈"); }};}public void maiPiao(){proxyobj1.maiPiao();}
}
- MaiPiaoProxy1:这是编译器为匿名内部类生成的类名。它实现了_DynamicProxy2MaiPiao:这是编译器为匿名内部类生成的类名。它实现了DynamicProxy_2MaiPiao接口,包含了你在匿名内部类中定义的方法体,即打印"我是代理哈哈哈"的maiPiao方法。
- MaiPiaoProxy: 这是主类,它同样实现了DynamicProxy2$MaiPiao接口。在它的构造函数中,创建了上述匿名内部类的一个实例(MaiPiaoProxy$1),并将其赋值给了成员变量proxyobj1。
为什么会有两个类?
- 外部类 (MaiPiaoProxy):这是你的原始定义,它需要保留,因为它是整个结构的入口点,包含了对外部的接口定义和对匿名内部类实例的管理。
- 匿名内部类 (MaiPiaoProxy$1):由于匿名内部类在功能上等同于一个独立的类,但出于编写便利或逻辑封装的目的而直接在另一个类中定义,编译器必须将其编译成一个单独的类文件以便于加载和执行。这样做保持了Java的类模型和面向对象特性。
最终完美实现
这个是我最新的完善的代码
public static class WanNengHuangNiu{public WanNengHuangNiu(Class<?> clazz) {String s = buildClassString(clazz);String className = clazz.getSimpleName() + "Proxy";final Map<String, byte[]> inMemoryClasses = new HashMap<>();compileCodeFromString(className, s,inMemoryClasses);//将文件MyClassLoader myClassLoader=new MyClassLoader();inMemoryClasses.forEach((k, v)-> myClassLoader.loadStringCode(k, v));Class<?> aClass;try {aClass = myClassLoader.loadClass(className);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}try {MaiPiao o = (MaiPiao) aClass.getConstructor().newInstance();o.maiPiao();} catch (Exception e) {throw new RuntimeException(e);}}private byte[] compileCodeFromString(String className, String code, Map<String, byte[]> inMemoryClasses) {JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();if (compiler == null) {throw new IllegalStateException("JDK required to compile at runtime");}DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();StandardJavaFileManager stdFileMgr = compiler.getStandardFileManager(null, null, null);MemoryFileManager fileManager = new MemoryFileManager(inMemoryClasses,stdFileMgr);JavaFileObject sourceFile = new StringJavaSource(className, code);JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, Collections.singletonList(sourceFile));boolean success = task.call();if (!success) {throw new RuntimeException("Compilation failed: " + diagnostics.getDiagnostics());}return inMemoryClasses.get(className);}static private class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {private final Map<String, byte[]> map;MemoryFileManager(Map<String, byte[]> map, JavaFileManager delegate) {super(delegate);this.map = map;}@Overridepublic JavaFileObject getJavaFileForOutput(Location location, String className,JavaFileObject.Kind kind, FileObject sibling) throws IOException {if (location == StandardLocation.CLASS_OUTPUT && kind == JavaFileObject.Kind.CLASS) {return createInMemoryClassFile(className);} else {return super.getJavaFileForOutput(location, className, kind, sibling);}}private JavaFileObject createInMemoryClassFile(String className) {URI uri = URI.create("memory:///" + className.replace('.', '/') + ".class");return new SimpleJavaFileObject(uri, JavaFileObject.Kind.CLASS) {@Overridepublic OutputStream openOutputStream() {return new ByteArrayOutputStream() {@Overridepublic void close() throws IOException {super.close();map.put(className, toByteArray());}};}};}}static class StringJavaSource extends SimpleJavaFileObject {final String code;StringJavaSource(String name, String code) {super(URI.create("string:///" + name.replaceAll("\\.","/") + Kind.SOURCE.extension), Kind.SOURCE);this.code = code;}@Overridepublic CharSequence getCharContent(boolean ignoreEncodingErrors) {return code;}}public String buildClassString(Class<?> clazz){String interfaceName = clazz.getSimpleName();StringBuilder stringBuilder=new StringBuilder();stringBuilder.append("import org.fpp.proxy.DynamicProxy2").append(";\n");stringBuilder.append("import org.fpp.proxy.DynamicProxy2.MaiPiao;").append("\n");stringBuilder.append("public class ").append(clazz.getSimpleName()).append("Proxy").append(" implements ").append(interfaceName).append("{").append("\n");//拼接被代理接口的作为代理类的属性stringBuilder.append(" private ").append(interfaceName).append(" proxyobj1;\n");//拼接代理类的构造函数stringBuilder.append(" public ").append(clazz.getSimpleName()).append("Proxy() {").append("\n");stringBuilder.append(" this.proxyobj1 = new ").append(interfaceName).append("(){").append("\n");//拼接实现的方法Method[] methods = clazz.getMethods();for (Method method:methods){stringBuilder.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(){").append("\n");stringBuilder.append(" System.out.println(\" 我是代理哈哈哈\");");stringBuilder.append(" }\n");}stringBuilder.append(" };\n");stringBuilder.append(" }\n");//拼接代理类 实现接口方法并调用被代理类的方法for (Method method:methods){stringBuilder.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(){").append("\n");stringBuilder.append(" proxyobj1.").append(method.getName()).append("();").append("\n");stringBuilder.append(" }\n");}stringBuilder.append("}\n");System.out.println(stringBuilder);return stringBuilder.toString();}}
运行结果如下
可以看到结果已成功打印
紧接着我们再改造一下代码,让他更好的实现动态代理
可以看到我们可以增加我们任意可以运行的代码
所以这个万能代,可以代理一切 可以增加任意我们想增加的逻辑,可以接下来看我们的输出
package org.fpp.proxy;import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;/*** @author bigbird-0101* @date 2024-06-20 22:52*/
public class DynamicProxy2 {public static void main(String[] args) {WanNengHuangNiu wanNengHuangNiu = new WanNengHuangNiu(MaiPiao.class,"System.out.println(\"动态代码\");");MaiPiao proxyObj = (MaiPiao) wanNengHuangNiu.getProxyObj();proxyObj.maiPiao();WanNengHuangNiu wanNengHuangNiuLeiJun = new WanNengHuangNiu(MaiShouJi.class,"System.out.println(\"雷军卖手机\");");MaiShouJi maiShouJi = (MaiShouJi) wanNengHuangNiuLeiJun.getProxyObj();maiShouJi.maiShouJi();}public static class WanNengHuangNiu{private Object proxyObj;public WanNengHuangNiu(Class<?> clazz,String dynamicCode) {String s = buildClassString(clazz,dynamicCode);String className = clazz.getSimpleName() + "Proxy";final Map<String, byte[]> inMemoryClasses = new HashMap<>();CodeCompilerUtil.compileCodeFromString(className, s,inMemoryClasses);//将文件MyClassLoader myClassLoader=new MyClassLoader();inMemoryClasses.forEach(myClassLoader::loadStringCode);Class<?> aClass;try {aClass = myClassLoader.loadClass(className);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}try {proxyObj= aClass.getConstructor().newInstance();} catch (Exception e) {throw new RuntimeException(e);}}public Object getProxyObj() {return proxyObj;}public String buildClassString(Class<?> clazz, String dynamicCode){String interfaceName = clazz.getSimpleName();StringBuilder stringBuilder=new StringBuilder();String replace = clazz.getName().replaceAll("\\$", ".");stringBuilder.append("import ").append(replace).append(";\n");stringBuilder.append("public class ").append(clazz.getSimpleName()).append("Proxy").append(" implements ").append(interfaceName).append("{").append("\n");//拼接被代理接口的作为代理类的属性stringBuilder.append(" private ").append(interfaceName).append(" proxyobj1;\n");//拼接代理类的构造函数stringBuilder.append(" public ").append(clazz.getSimpleName()).append("Proxy() {").append("\n");stringBuilder.append(" this.proxyobj1 = new ").append(interfaceName).append("(){").append("\n");//拼接实现的方法Method[] methods = clazz.getMethods();for (Method method:methods){stringBuilder.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(){").append("\n");stringBuilder.append(" System.out.println(\" 我是代理哈哈哈\");");stringBuilder.append(dynamicCode);stringBuilder.append(" }\n");}stringBuilder.append(" };\n");stringBuilder.append(" }\n");//拼接代理类 实现接口方法并调用被代理类的方法for (Method method:methods){stringBuilder.append(" public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(){").append("\n");stringBuilder.append(" proxyobj1.").append(method.getName()).append("();").append("\n");stringBuilder.append(" }\n");}stringBuilder.append("}\n");System.out.println(stringBuilder);return stringBuilder.toString();}}public static class MyClassLoader extends ClassLoader {public Class<?> loadStringCode(String className, byte[] bytes){Class<?> clazz = defineClass(className,bytes,0,bytes.length);return clazz;}}public static class MaiPiaoProxy implements MaiPiao {private MaiPiao liangjingRu;public MaiPiaoProxy() {liangjingRu = new MaiPiao() {@Overridepublic void maiPiao() {}};}@Overridepublic void maiPiao(){liangjingRu.maiPiao();}}public interface MaiPiao{void maiPiao();}public static class MaiShouJiProxy implements MaiShouJi {private MaiShouJi maiShouJi;public MaiShouJiProxy() {maiShouJi = new MaiShouJi() {@Overridepublic void maiShouJi() {}};}@Overridepublic void maiShouJi() {maiShouJi.maiShouJi();}}public interface MaiShouJi{void maiShouJi();}
}
输出结果
会话6:深入理解jdk动态代理
- 👧 哇哦,哥哥好厉害哦,好厉害好厉害
- 👨哈哈哈哈哈,马马虎虎了
- 👧 哥哥,我听说java自己就自带动态代理,你可以帮我分析一下原理吗?
- 👨好的啊,我猜他的思路应该和我差不多
jdk怎么实现动态代理的呢?
我搞了一个接口 一个有返回参数的 一个没有返回参数的,还有一个调自己的
package org.fpp.proxy;import java.lang.reflect.Proxy;/*** @author bigbird-0101* @date 2024-06-25 21:58*/
public class JdkDynamicProxy {public static void main(String[] args) {MaiPiao proxyMaiPiao = (MaiPiao) Proxy.newProxyInstance(JdkDynamicProxy.class.getClassLoader(), new Class[]{MaiPiao.class}, (proxy, method, args1) -> {if(method.getName().equals("maiPiao")){System.out.println("卖票");return null;}else if(method.getName().equals("getName")){return "name1";}return method.invoke(proxy);});proxyMaiPiao.maiPiao();String name = proxyMaiPiao.getName();System.out.println(name);proxyMaiPiao.doSomething();}public interface MaiPiao{void maiPiao();String getName();void doSomething();}
}
**查看输出 **
分析
InvocationHandler相当于我们自己实现里面的动态代码,添加自己任意的逻辑
proxyMaiPiao 和 proxy是同一个对象,当代码执行到24行时,会触发 19行,导致一直调用自己调用自己死循环了。那为啥会提示这个异常呢?下面我们探讨一下。
深入理解
newProxyInstance 这个方法到底干啥了?咋实现的?
可以看到逻辑非常简单 创建对象,肯定先找到构造函数啊,再通过构造函数去构造对象,来来来 我们继续分析。
怎么去构造 构造函数呢?可以看到这里有个生成代理构造函数的缓存,避免重复生成了,然后主要是ProxyBuilder里的build方法 我们debug去看看。
build分析
Constructor<?> build() {//生成代理类Class<?> proxyClass = defineProxyClass(context, interfaces);//获取代理类的构造函数final Constructor<?> cons;try {//private static final Class<?>[] constructorParams ={ InvocationHandler.class };//获取 构造函数参数 是这个InvocationHandler的构造函数cons = proxyClass.getConstructor(constructorParams);} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});return cons;}
怎么去生成代理类的呢?
1.生成类名
2.生成符合java规范的字节码二进制
3.让类加载器加载这个字节码二进制
private static Class<?> defineProxyClass(ProxyClassContext context, List<Class<?>> interfaces) {/** Choose a name for the proxy class to generate.*/long num = nextUniqueNumber.getAndIncrement();//$Proxy代理开头的proxy类名String proxyName = context.packageName().isEmpty()? proxyClassNamePrefix + num: context.packageName() + "." + proxyClassNamePrefix + num;ClassLoader loader = getLoader(context.module());trace(proxyName, context.module(), loader, interfaces);/** Generate the specified proxy class.*///生成符合java规范的字节码二进制byte[] proxyClassFile = ProxyGenerator.generateProxyClass(loader, proxyName, interfaces,context.accessFlags() | Modifier.FINAL);try {//类加载器加载这个字节码二进制Class<?> pc = JLA.defineClass(loader, proxyName, proxyClassFile,null, "__dynamic_proxy__");reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);return pc;} catch (ClassFormatError e) {/** A ClassFormatError here means that (barring bugs in the* proxy class generation code) there was some other* invalid aspect of the arguments supplied to the proxy* class creation (such as virtual machine limitations* exceeded).*/throw new IllegalArgumentException(e.toString());}}
来来来继续他是咋生成的呢?
可以看到每一个代理类都是通过 ProxyGenerator来生成的,
好吧 是通过asm框架来生成的
private byte[] generateClassFile() {//生成java开头那么类声明和实现哪个接口visit(CLASSFILE_VERSION, accessFlags, dotToSlash(className), null,JLR_PROXY, typeNames(interfaces));/** Add proxy methods for the hashCode, equals,* and toString methods of java.lang.Object. This is done before* the methods from the proxy interfaces so that the methods from* java.lang.Object take precedence over duplicate methods in the* proxy interfaces.*///添加一个公共方法addProxyMethod(hashCodeMethod);addProxyMethod(equalsMethod);addProxyMethod(toStringMethod);/** Accumulate all of the methods from the proxy interfaces.*///添加代理方法for (Class<?> intf : interfaces) {for (Method m : intf.getMethods()) {if (!Modifier.isStatic(m.getModifiers())) {addProxyMethod(m, intf);}}}/** For each set of proxy methods with the same signature,* verify that the methods' return types are compatible.*/for (List<ProxyMethod> sigmethods : proxyMethods.values()) {checkReturnTypes(sigmethods);}//生成构造函数generateConstructor();for (List<ProxyMethod> sigmethods : proxyMethods.values()) {for (ProxyMethod pm : sigmethods) {// add static field for the Method objectvisitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, pm.methodFieldName,LJLR_METHOD, null, null);// Generate code for proxy methodpm.generateMethod(this, className);}}generateStaticInitializer();generateLookupAccessor();return toByteArray();}
最后我们可以看看 他生成的文件是啥样的?怎么看呢?浓眉大眼的观众应该发现了。
来来来我们来设置一下
-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
jdk帮我们生成的代码如下所示。 他是继承了 Proxy,然后实现了我们的接口,**这个也很好的解释了为啥jdk只支持接口,不能支持类?**因为java不能多继承,他自己本身就要继承Proxy
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package jdk.proxy1;import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.fpp.proxy.JdkDynamicProxy;public final class $Proxy0 extends Proxy implements JdkDynamicProxy.MaiPiao {private static final Method m0;private static final Method m1;private static final Method m2;private static final Method m3;private static final Method m4;private static final Method m5;public $Proxy0(InvocationHandler var1) {super(var1);}public final int hashCode() {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 boolean equals(Object var1) {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final String toString() {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 String getName() {try {return (String)super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void doSomething() {try {super.h.invoke(this, m4, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void maiPiao() {try {super.h.invoke(this, m5, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {ClassLoader var0 = $Proxy0.class.getClassLoader();try {m0 = Class.forName("java.lang.Object", false, var0).getMethod("hashCode");m1 = Class.forName("java.lang.Object", false, var0).getMethod("equals", Class.forName("java.lang.Object", false, var0));m2 = Class.forName("java.lang.Object", false, var0).getMethod("toString");m3 = Class.forName("org.fpp.proxy.JdkDynamicProxy$MaiPiao", false, var0).getMethod("getName");m4 = Class.forName("org.fpp.proxy.JdkDynamicProxy$MaiPiao", false, var0).getMethod("doSomething");m5 = Class.forName("org.fpp.proxy.JdkDynamicProxy$MaiPiao", false, var0).getMethod("maiPiao");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {return MethodHandles.lookup();} else {throw new IllegalAccessException(var0.toString());}}
}
所以这就解释了为啥会抛出这个异常 UndeclaredThrowableException,但是为啥又抛出这个异常呢
Exception in thread “main” java.lang.NoClassDefFoundError: Could not initialize class java.lang.reflect.UndeclaredThrowableException
可以看到他说不能构造 UndeclaredThrowableException,为啥?然后我自己先构造试一下,发现了我们预期的栈溢出异常。为啥动态代理new UndeclaredThrowableException的时候不能 构造成功 难道和类加载器有关?
这个类加载没有加载 UndeclaredThrowableException 这个类,所以导致他不能构造?但是这个不可能啊,这个类是java 反射模块当中的类,是java基础模块的类不可能没有加载啊。所以肯定另有原因。
所以我就debug尝试 发现 卧槽居然有这样的事情 UndeclaredThrowableException 能获取到他的 Class对象,但是这个Class对象的classloader居然是空的,这个是为啥呢?这个是因为java.base模块的类默认是由引导类加载器(Bootstrap ClassLoader)加载的,而不是普通的类加载器。引导类加载器在JVM内部实现,没有具体的ClassLoader实例,因此当你尝试获取java.base模块中类的类加载器时,结果为null
挖槽 我感觉我发现了什么,是不是jdk有bug?我使用的是jdk21
然后我尝试使用jdk11发现是正常的 ,是我们预期的栈溢出。那为啥jdk21会抛出这个异常呢?
bug单已提 单号: 9077228
好的jdk的动态代理就这样完结了。怎么样?妹妹
会话7:结束
- 👧 哇哦,哥哥好厉害哦,好厉害好厉害
- 👨哈哈哈哈哈,这还不是洒洒水。
- 👧 哥哥,我还想知道spring怎么做动态代理的,你可以帮我分析一下原理吗?
- 👨好的啊,下一篇咱们继续说。