星辰计划01-动态代理

会话1: 什么是动态代理?

  • 👧 什么是代理啊?
  • 👨来来来,听我细细来说

代理这个词在不同的上下文中有不同的含义,主要可以归纳为以下几类解释:

  1. 计算机网络中的代理服务器(Proxy Server): 代理服务器是在网络中充当中间人的系统,它位于客户端和目标服务器之间。它的主要作用包括提供访问控制、数据缓存、匿名浏览、安全过滤等功能。代理服务器可以在开放系统互联(OSI)模型的会话层工作,起到防火墙的作用,帮助用户或网络访问Internet资源,同时隐藏用户的真实身份或提高访问速度。
  2. 编程中的代理(Delegate): 在编程语言如C#中,代理(Delegate)是一种引用类型,类似于C++中的函数指针,但更加强大和类型安全。它允许开发者将方法作为参数传递给其他方法,或者将方法存储在变量中供后续调用。委托是面向对象的方式实现的,可以理解为封装了方法调用的一种类型,使得方法像对象一样可以被操作和传递。
  3. 法律意义上的代理: 法律上的代理是指一种法律安排,其中一方(代理人)在另一方(被代理人)的授权下,代表被代理人在法律规定的范围内与第三方进行民事活动。这种行为的法律后果直接由被代理人承担。代理可以分为直接代理(显名代理)和间接代理(隐名代理),并且依据代理权产生的原因不同,还可以分为委托代理、法定代理和指定代理等。代理涉及到至少三方当事人:被代理人、代理人和第三人。

总结来说,代理既可以指技术领域中用于中转、控制或代表客户端进行网络请求的服务器,也可以是编程语言中的一个概念,用以实现方法的动态调用和事件处理,还可能是法律框架内代表他人行事并承担相应法律后果的一种法律行为。
下面几种都可以认为是代理
卖周杰伦演唱会门票黄牛,老板的小秘书
本来通过官方购票渠道购买,但是由于购票困难,通过黄牛帮我们做个代理帮我去买票,让他帮我们去买票,他就是我们的代理,他们也是买票平台的代理
图片描述
还有技术上的 nginx 反向代理转发请求
图片描述2

会话2: 动态代理与静态代理

  • 👧 那什么是动态代理啊?有动态代理那是不是有静态代理
  • 👨 哇厉害厉害!都学会举一反三了。

简单来说静态代理能够代表某一个或者某一类去做操作,而动态代理则是更加灵活,能够动态的代表所有,能够操作这个代理来间接操作任何他所代表的。
静态代理在不同的领域有着不同的含义,主要可以分为以下两类:

  1. 计算机网络领域的静态代理IP: 这是指一种个人代理服务器服务,其中提供的IP地址是固定不变的,专为单个用户分配和使用,不与其他用户共享。静态代理IP地址的稳定性和专享性使其在需要高度匿名性、稳定连接和特定IP信誉的场景中尤为有用,例如社交媒体账户管理、电子商务运营、数据抓取等。由于这些特性,静态代理往往价格较高,但提供了更好的控制度和安全性。
  2. 软件工程中的静态代理设计模式: 在软件开发中,静态代理是一种设计模式,指的是在程序编译期间就已经确定的代理类。这意味着代理类的字节码文件在程序运行前就已经存在,代理类和目标类(即被代理的类)之间的关系是预先定义好的。静态代理通过扩展目标类的功能,可以在不修改目标类代码的情况下,为对象增加额外的功能,比如日志记录、权限控制、性能监控等。然而,这种方式不够灵活,如果目标类的接口发生变化,代理类也需要相应地调整,增加了维护成本。

综上所述,无论是网络领域的静态代理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("卖一张周杰伦演唱会门票");}}
}

可以看到已经买到的一张门票了
image.png

  • 👧 哇哦,哥哥好厉害哦,但是人家还想去奶茶刘若英的演唱会,咋办?
  • 👨 这还不简单 看我的操作,嘿嘿
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();}
}

怎么实现这个万能代对象?可以看到普通的下面的买票的代理 和 卖手机的代理实现过程。我们找一下相同点。

  1. 实现这个代理 我们都实现了一个接口
  2. 并在这个代理内部构建一个接口的实现。 作为类的属性变量,并且代理类也实现这个接口,并调用这个被代理对象

我怎么基于 上面两个相同点来写一个万能的?既然要求是万能的?说明这个类也是一直在变化的,我能不能动态的生成这个类呢?在万能代这个对象当中呢?
来我们尝试一下(前提是被代理的一个接口)

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();}
}

查看程序的输出,可以看到我们已生成买票的代理类代码,但是我们没有补充实现买票的接口这个时候我们继续生成代码。
image.png

进阶实现

改造后的代码
我们构造了被代理接口的实现

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 来实现接口的方法,并调用我们的内部接口实现类
image.png

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();}}

查看结果 **
已经按我们的预期输出
image.png
可以看到我们已经生成了很完整的代码?那么问题来了我们怎么让我们生成的代码跑起来呢?
通过类加载器加载这个我们已经生成的代码,但是我们的java类加载器并没有那种直接加载string代码的方法。那咋办 我们仔细阅读classloader的源码,发现有一个 defineClass,这个方法试讲字节转class对象这个是我们想要的,我们
把我们生成的代码转成byte,然后通过这个方法再次转成class,转成class后我们就能构造对象了**。但是由于这个方法是 protected的 所以只能被他的子类调用所以这里我们需要自定义类加载器。
image.png

完善实现

自定义类加载器

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明显不是字节码的标准。所以提示这个,那怎么解决这个问题呢?
image.png
利用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();}}

输出结果
可以看到已经成功编译
image.png
这个是我生成的代码 我遇到了一个有意思的问题 这段代码 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();}
}

image.png
image.png
image.png

  • MaiPiaoProxy1:这是编译器为匿名内部类生成的类名。它实现了_DynamicProxy2MaiPiao:这是编译器为匿名内部类生成的类名。它实现了DynamicProxy_2MaiPiao接口,包含了你在匿名内部类中定义的方法体,即打印"我是代理哈哈哈"的maiPiao方法。
  • MaiPiaoProxy: 这是主类,它同样实现了DynamicProxy2$MaiPiao接口。在它的构造函数中,创建了上述匿名内部类的一个实例(MaiPiaoProxy$1),并将其赋值给了成员变量proxyobj1。

为什么会有两个类?

  1. 外部类 (MaiPiaoProxy):这是你的原始定义,它需要保留,因为它是整个结构的入口点,包含了对外部的接口定义和对匿名内部类实例的管理。
  2. 匿名内部类 (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();}}

运行结果如下
可以看到结果已成功打印

image.png
紧接着我们再改造一下代码,让他更好的实现动态代理
可以看到我们可以增加我们任意可以运行的代码
所以这个万能代,可以代理一切 可以增加任意我们想增加的逻辑,可以接下来看我们的输出

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();}
}

输出结果
image.png

会话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();}
}

**查看输出 **
image.png
分析
InvocationHandler相当于我们自己实现里面的动态代码,添加自己任意的逻辑
proxyMaiPiao 和 proxy是同一个对象,当代码执行到24行时,会触发 19行,导致一直调用自己调用自己死循环了。那为啥会提示这个异常呢?下面我们探讨一下。

深入理解

newProxyInstance 这个方法到底干啥了?咋实现的?
可以看到逻辑非常简单 创建对象,肯定先找到构造函数啊,再通过构造函数去构造对象,来来来 我们继续分析。
image.png
怎么去构造 构造函数呢?可以看到这里有个生成代理构造函数的缓存,避免重复生成了,然后主要是ProxyBuilder里的build方法 我们debug去看看。
image.png
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来生成的,
image.png
好吧 是通过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();}

最后我们可以看看 他生成的文件是啥样的?怎么看呢?浓眉大眼的观众应该发现了。
image.png
image.png
来来来我们来设置一下
-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
image.png
可以看到他说不能构造 UndeclaredThrowableException,为啥?然后我自己先构造试一下,发现了我们预期的栈溢出异常。为啥动态代理new UndeclaredThrowableException的时候不能 构造成功 难道和类加载器有关?
这个类加载没有加载 UndeclaredThrowableException 这个类,所以导致他不能构造?但是这个不可能啊,这个类是java 反射模块当中的类,是java基础模块的类不可能没有加载啊。所以肯定另有原因。
image.png
所以我就debug尝试 发现 卧槽居然有这样的事情 UndeclaredThrowableException 能获取到他的 Class对象,但是这个Class对象的classloader居然是空的,这个是为啥呢?这个是因为java.base模块的类默认是由引导类加载器(Bootstrap ClassLoader)加载的,而不是普通的类加载器。引导类加载器在JVM内部实现,没有具体的ClassLoader实例,因此当你尝试获取java.base模块中类的类加载器时,结果为null
image.png
挖槽 我感觉我发现了什么,是不是jdk有bug?我使用的是jdk21
然后我尝试使用jdk11发现是正常的 ,是我们预期的栈溢出。那为啥jdk21会抛出这个异常呢?
bug单已提 单号: 9077228
好的jdk的动态代理就这样完结了。怎么样?妹妹

会话7:结束

  • 👧 哇哦,哥哥好厉害哦,好厉害好厉害
  • 👨哈哈哈哈哈,这还不是洒洒水。
  • 👧 哥哥,我还想知道spring怎么做动态代理的,你可以帮我分析一下原理吗?
  • 👨好的啊,下一篇咱们继续说。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/38865.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

跨平台Ribbon UI组件QtitanRibbon全新发布v6.7.0——支持Qt 6.6.3

没有Microsoft在其办公解决方案中提供的界面&#xff0c;就无法想象现代应用程序&#xff0c;这个概念称为Ribbon UI&#xff0c;目前它是使应用程序与时俱进的主要属性。QtitanRibbon是一款遵循Microsoft Ribbon UI Paradigm for Qt技术的Ribbon UI组件&#xff0c;QtitanRibb…

Linux_生产消费模型_Block_Queue

目录 一、互斥锁 1.1 错误的抢票 1.1.1 类的成员函数与构造 1.1.2 start 函数 1.1.3 线程的回调函数 1.1.4 main 函数 1.1.5 结果 1.2 概念 1.3 相关系统调用 1.3.1 锁的创建 1.3.2 锁的初始化 1.3.2.1 动态初始化 1.3.2.2 静态初始化 1.3.3 锁的销毁 1.3.4…

看不懂懂车大爆炸,你就错过了国产小车的王炸!

咦&#xff1f;咋的啦&#xff1f;咱中国自己的汽车品牌前几天在汽车工业协会公布的数据里一跃而起&#xff0c;真的是威风凛凛啊&#xff01;2023年咱们自家的乘用车品牌市场份额硬生生地占了个56%&#xff0c;这可是半壁江山啊&#xff01;特别是那些10万块钱以下的家用小车&…

32.哀家要长脑子了!

1.299. 猜数字游戏 - 力扣&#xff08;LeetCode&#xff09; 公牛还是挺好数的&#xff0c;奶牛。。。妈呀&#xff0c;一朝打回解放前 抓本质抓本质&#xff0c;有多少位非公牛数可以通过重新排列转换公牛数字&#xff0c;意思就是&#xff0c;当这个数不是公牛数字时&#x…

C++多态~~的两个特殊情况

目录 1.多态的概念 2.简单认识 &#xff08;1&#xff09;一个案例 &#xff08;2&#xff09;多态的两个满足条件 &#xff08;3&#xff09;虚函数的重写 &#xff08;4&#xff09;两个特殊情况 1.多态的概念 &#xff08;1&#xff09;多态就是多种形态&#xff1b; …

SQL 29 计算用户的平均次日留存率题解

问题截图如下&#xff1a; SQL建表代码&#xff1a; drop table if exists user_profile; drop table if exists question_practice_detail; drop table if exists question_detail; CREATE TABLE user_profile ( id int NOT NULL, device_id int NOT NULL, gender varchar…

小白也能懂:逆向分析某网站加速乐Cookie参数流程详解

加速乐作为一种常见的反爬虫技术&#xff0c;在网络上已有大量详尽深入的教程可供参考。然而&#xff0c;对于那些初次接触的人来说&#xff0c;直接面对它可能仍会感到困惑。 声明 本文仅用于学习交流&#xff0c;学习探讨逆向知识&#xff0c;欢迎私信共享学习心得。如有侵权…

【区块链+基础设施】珠三角征信链 | FISCO BCOS应用案例

“珠三角征信链”是中国人民银行广州分行、中国人民银行深圳市中心支行按照中国人民银行总行工作部署&#xff0c;积 极贯彻珠三角一体化发展、粤港澳大湾区建设等国家战略而建设的跨区域征信一体化数据中心枢纽&#xff0c;以 FISCO BCOS 为底链构建应用平台&#xff0c;并由微…

springboot接口防抖【防重复提交】

什么是防抖 所谓防抖&#xff0c;一是防用户手抖&#xff0c;二是防网络抖动。在Web系统中&#xff0c;表单提交是一个非常常见的功能&#xff0c;如果不加控制&#xff0c;容易因为用户的误操作或网络延迟导致同一请求被发送多次&#xff0c;进而生成重复的数据记录。要针对用…

Docker 镜像导出和导入

docker 镜像导出 # 导出 docker 镜像到本地文件 docker save -o [输出文件名.tar] [镜像名称[:标签]] # 示例 docker save -o minio.tar minio/minio:latest-o 或 --output&#xff1a;指定导出文件的路径和名称[镜像名称[:标签]]&#xff1a;导出镜像名称以及可选的标签 dock…

【Python画图-驯化01】一文叫你搭建python画图最优环境配置

【Python画图-循环01】一文叫你搭建python画图最优环境配置 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获取相关内容文档关注&#…

Windows/Linux/Mac 系统局域网服务发现协议及传输速度比较

简介 分析 / 验证对比常见局域网服务发现协议在 Windows/Linux/Mac 等不同系统下的支持和表现 在使用不同系统的智能硬件时&#xff0c;如常见的树莓派 / Openwrt 路由器 / Debian/Fedora/Windows/Mac 等系统是&#xff0c;系统间相互发现以及网络共享本应是系统的基础服务&a…

探秘 Django 专业之道

一、Django项目开发 1.web框架底层 1.1 网络通信 注意&#xff1a;局域网 个人一般写程序&#xff0c;想要让别人访问&#xff1a;阿里云、腾讯云。 去云平台租服务器&#xff08;含公网IP&#xff09;程序放在云服务器 先以局域网为例 我的电脑【服务端】 import sock…

Linux下SUID提权学习 - 从原理到使用

目录 1. 文件权限介绍1.1 suid权限1.2 sgid权限1.3 sticky权限 2. SUID权限3. 设置SUID权限4. SUID提权原理5. SUID提权步骤6. 常用指令的提权方法6.1 nmap6.2 find6.3 vim6.4 bash6.5 less6.6 more6.7 其他命令的提权方法 1. 文件权限介绍 linux的文件有普通权限和特殊权限&a…

计算机毕业设计Python深度学习美食推荐系统 美食可视化 美食数据分析大屏 美食爬虫 美团爬虫 机器学习 大数据毕业设计 Django Vue.js

Python美食推荐系统开题报告 一、项目背景与意义 随着互联网和移动技术的飞速发展&#xff0c;人们的生活方式发生了巨大变化&#xff0c;尤其是餐饮行业。在线美食平台如雨后春笋般涌现&#xff0c;为用户提供了丰富的美食选择。然而&#xff0c;如何在海量的餐饮信息中快速…

(1)Jupyter Notebook 下载及安装

目录 1. Jupyter Notebook是什么&#xff1f;2. Jupyter Notebook特征3. 应用3. 利用Google Colab安装Jupyter Notebook3.1 什么是 Colab&#xff1f;3.2 访问 Google Colab 1. Jupyter Notebook是什么&#xff1f; 百度百科: Jupyter Notebook&#xff08;此前被称为 IPython …

Unity Shader 软粒子

Unity Shader 软粒子 前言项目Shader连连看项目渲染管线设置 鸣谢 前言 当场景有点单调的时候&#xff0c;就需要一些粒子点缀&#xff0c;此时软粒子就可以发挥作用了。 使用软粒子与未使用软粒子对比图 项目 Shader连连看 这里插播一点&#xff0c;可以用Vertex Color与…

ARP 原理详解 二

只要确定了 IP 地址后&#xff0c;就能够向这个 IP 地址所在的主机发送数据报&#xff0c;这是我们所熟知的事情。 但是再往深了想&#xff0c;IP 地址只是标识网络层的地址&#xff0c;那么在网络层下方数据链路层是不是也有一个地址能够告诉对方主机自己的地址呢&#xff1f…

生产环境部署与协同开发-Docker(原创超全)

关闭防火墙 systemctl stop firewalld.service 关闭SELinux vim /etc/selinux/config 查看yum支持的包并安装docker引擎 yum listyum install -y docker 启动docker设置docker自启动测试docker是否安装成功&#xff1f; systemctl start dockersystemctl enable dockerdoc…

算法基础-----【动态规划】

动态规划(待完善) 动规五部曲分别为&#xff1a; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式&#xff08;状态转移公式&#xff09;dp数组如何初始化确定遍历顺序举例推导dp数组、 动态规划的核心就是递归剪枝&#xff08;存储键值&#xff0c;…