前言
开心一刻
周末, 带着老婆儿子一起逛公园. 儿子一个人跑在前面, 吧唧一下不小心摔了一跤, 脑袋瓜子摔了个包, 稀里哗啦的哭道:"爸爸, 我会不会摔成傻子!"
我指了指我头上的伤痕安慰道:"不会的, 你看, 这是爸爸小时候摔的."
话还没有说话, 小家伙哭的更厉害了:"那就是说我长大后就会和你一样傻了, 我不要, 我不要!"
老婆忍不住发飙:"别哭了, 你怎么会变傻呢? 你看你爸, 你爸傻吗?"
我赶紧回应道:"是啊, 你看我多聪明!"
儿子:"真的, 不骗我?"
老婆:"当然!"
儿子:"可是如果老爸不是傻子, 当年怎么会娶你这个母老虎呢?"
我, 老婆:......
什么是代理模式
所谓代理, 就是一个人或者一个机构代表另一个人或者另一个机构采取行动. 在一些情况下, 一个客户不想或者不能直接引用一个对象, 而代理对象可以在客户端和目标对象之间起到中介的左右.
代理模式: 给某一个对象提供一个代理或占位符, 并由代理对象来控制对原对象的访问, 通过代理对象访问目标对象, 这样可以在不修改原目标对象的前提下, 提供额外的功能操作, 扩展目标对象的功能. 说简单点, 代理模式就是设置一个中间代理来控制访问原目标对象, 以达到增强原对象的功能和简化访问方式. 一般而言会分三种: 静态代理, 动态代理和 CGLIB 代理
代理模式结构如下:
静态代理
静态代理需要代理对象和被代理对象实现一样的接口, 我们来看个例子就清楚了
示例代理:
代理类: UserDaoProxy.java
- /**
- * 代理逻辑在代理类中, 而不是由用户自定义
- */
- public class UserDaoProxy implements IUserDao {
- private IUserDao target; // 被代理对象
- public UserDaoProxy(IUserDao target) {
- this.target = target;
- }
- /**
- * 前置 / 后置 处理一旦写完, 就固定死了, 后续想修改的话需要改此代理类
- * @param id
- * @return
- */
- public int delete(int id) {
- // 前置处理, 例如开启事务
- System.out.println("前置处理...");
- // 调用目标对象方法
- int count = target.delete(id);
- // 后置处理, 例如提交事务或事务回滚
- System.out.println("前置处理...");
- return count;
- }
- }
- View Code
UserDaoProxy 代理 IUserDao 类型, 此时也只能代理 IUserDao 类型的被代理对象. 测试结果就不展示了, 相信大家看了代码也知道了
优点: 可以在不修改目标对象的前提下扩展目标对象的功能
缺点: 如果需要代理多个类, 每个类都会有一个代理类, 会导致代理类无限制扩展; 如果类中有多个方法, 同样的代理逻辑需要反复实现, 应用到每个方法上, 一旦接口增加方法, 目标对象与代理对象都要进行修改
一个静态代理只能代理一个类, 那么有没有什么方式可以实现同一个代理类来代理任意对象呢? 肯定有的, 也就是下面讲到的: 动态代理
动态代理
代理类在程序运行时创建的代理方式被成为动态代理. 也就是说, 这种情况下, 代理类并不是在 Java 代码中定义的, 而是在运行时根据我们在 Java 代码中的 "指示" 动态生成的. 下面我们一步一步手动来实现动态代理. 下面的示例都是直接针对接口的, 就不是针对接口的具体实现类了, 静态代理示例中, UserDaoProxy 代理的是 IUserDao 的实现类: UserDaoImpl, 那么动态代理示例就直接针对接口了, 下面示例针对的都是 UserMapper 接口, 模拟的 mybatis, 但不局限于 UserMapper 接口
代理类源代码持久化
1, 先利用反射动态生成代理类, 并持久化代理类到磁盘(也就是生成代理类的 java 源文件),generateJavaFile 方法如下
- /**
- * 生成接口实现类的源代码, 并持久化到 java 文件
- * @param interface_
- * @param proxyJavaFileDir
- * @throws Exception
- */
- private static void generateJavaFile(Class<?> interface_, String proxyJavaFileDir) throws Exception {
- StringBuilder proxyJava = new StringBuilder();
- proxyJava.append("package").append(interface_.getPackage().getName()).append(";").append(ENTER).append(ENTER)
- .append("public class").append(PROXY_CLASS_NAME).append("implements").append(interface_.getName()).append("{");
- Method[] methods = interface_.getMethods();
- for(Method method : methods) {
- Type returnType = method.getGenericReturnType();
- Type[] paramTypes = method.getGenericParameterTypes();
- proxyJava.append(ENTER).append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
- .append(TAB_STR).append("public").append(returnType.getTypeName()).append("").append(method.getName()).append("(");
- for(int i=0; i<paramTypes.length; i++) {
- if (i != 0) {
- proxyJava.append(",");
- }
- proxyJava.append(paramTypes[i].getTypeName()).append("param").append(i);
- }
- proxyJava.append(") {").append(ENTER)
- .append(TAB_STR).append(TAB_STR)
- .append("System.out.println(\" 数据库操作, 并获取执行结果...\");").append(ENTER); // 真正数据库操作, 会有返回值, 下面的 return 返回应该是此返回值
- if (!"void".equals(returnType.getTypeName())) {
- proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER); // 这里的 "null" 应该是上述中操作数据库后的返回值, 为了演示写成了 null
- }
- proxyJava.append(TAB_STR).append("}").append(ENTER);
- }
- proxyJava .append("}");
- // 写入文件
- File f = new File(proxyJavaFileDir + PROXY_CLASS_NAME + ".java");
- FileWriter fw = new FileWriter(f);
- fw.write(proxyJava.toString());
- fw.flush();
- fw.close();
- }
- View Code
生成的代理类:$Proxy0.java 如下
- package com.lee.mapper;
- public class $Proxy0 implements com.lee.mapper.UserMapper {
- @Override
- public java.lang.Integer save(com.lee.model.User param0) {
- System.out.println("数据库操作, 并获取执行结果...");
- return null;
- }
- @Override
- public com.lee.model.User getUserById(java.lang.Integer param0) {
- System.out.println("数据库操作, 并获取执行结果...");
- return null;
- }
- }
- View Code
这个代理类的生成过程是我们自己实现的, 实现不难, 但排版太繁琐, 我们可以用 https://github.com/square/javapoet 来生成代理类源代码, generateJavaFileByJavaPoet 方法如下
- /**
- * 用 JavaPoet 生成接口实现类的源代码, 并持久化到 java 文件
- * @param interface_ 目标接口类
- * @return
- */
- public static void generateJavaFileByJavaPoet(Class<?> interface_) throws Exception {
- // 类名, 实现的接口, 以及类访问限定符
- TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("JavaPoet$Proxy0")
- .addSuperinterface(interface_)
- .addModifiers(Modifier.PUBLIC);
- Method[] methods = interface_.getDeclaredMethods();
- for (Method method : methods) {
- // 方法参数列表
- List<ParameterSpec> paramList = new ArrayList<>();
- Type[] paramTypes = method.getGenericParameterTypes();
- int count = 1 ;
- for (Type param : paramTypes) {
- ParameterSpec paramSpec = ParameterSpec.builder(Class.forName(param.getTypeName()), "param" + count)
- .build();
- count ++;
- paramList.add(paramSpec);
- }
- // 方法名, 方法访问限定符, 参数列表, 返回值, 方法体等
- Class<?> returnType = method.getReturnType();
- MethodSpec.Builder builder = MethodSpec.methodBuilder(method.getName())
- .addModifiers(Modifier.PUBLIC)
- .addParameters(paramList)
- .addAnnotation(Override.class)
- .returns(returnType)
- .addCode("\n")
- .addStatement("$T.out.println(\" 数据库操作, 并获取执行结果...\")", System.class) // 真正数据库操作, 会有返回值, 下面的 return 返回应该是此返回值
- .addCode("\n");
- if (!"void".equals(returnType.getName())) {
- builder.addStatement("return null"); // 这里的 "null" 应该是上述中操作数据库后的返回值, 为了演示写成了 null
- }
- MethodSpec methodSpec = builder.build();
- typeSpecBuilder.addMethod(methodSpec);
- }
- JavaFile javaFile = JavaFile.builder(interface_.getPackage().getName(), typeSpecBuilder.build()).build();
- javaFile.writeTo(new File(SRC_JAVA_PATH));
- }
- View Code
生成的代理类: JavaPoet$Proxy0.java 如下
- package com.lee.mapper;
- import com.lee.model.User;
- import java.lang.Integer;
- import java.lang.Override;
- import java.lang.System;
- public class JavaPoet$Proxy0 implements UserMapper {
- @Override
- public Integer save(User param1) {
- System.out.println("数据库操作, 并获取执行结果...");
- return null;
- }
- @Override
- public User getUserById(Integer param1) {
- System.out.println("数据库操作, 并获取执行结果...");
- return null;
- }
- }
- View Code
利用 javapoet 生成的代理类更接近我们平时手动实现的类, 排版更符合我们的编码习惯, 看上去更自然一些; 两者的实现过程是一样的, 只是 javapoet 排版更好
2, 既然代理类的源代码已经有了, 那么需要对其编译了, compileJavaFile 方法如下
- /**
- * 编译代理类源代码生成代理类 class
- * @param proxyJavaFileDir
- */
- private static void compileJavaFile(String proxyJavaFileDir) throws Exception {
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- // 获得文件管理者
- StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
- Iterable<? extends JavaFileObject> fileObjects = manager.getJavaFileObjects(proxyJavaFileDir + PROXY_CLASS_NAME + ".java");
- // 编译任务
- JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, fileObjects);
- // 开始编译, 执行完可在指定目录下看到. class 文件
- task.call();
- // 关闭文件管理者
- manager.close();
- }
- View Code
会在指定目录下看到:$Proxy0.class
3, 加载 $Proxy0.class, 并创建其实例对象(代理实例对象)
- public static <T> T newInstance(Class<T> interface_) throws Exception{
- String proxyJavaFileDir = SRC_JAVA_PATH + interface_.getPackage().getName().replace(".", File.separator) + File.separator;
- // 1, 生成 interface_接口的实现类, 并持久化到磁盘:$Proxy0.java
- generateJavaFile(interface_, proxyJavaFileDir);
- // 2, 编译 $Proxy0.java, 生成 $Proxy0.class 到磁盘
- compileJavaFile(proxyJavaFileDir);
- // 3, 加载 $Proxy0.class, 并创建其实例对象(代理实例对象)
- MyClassLoader loader = new MyClassLoader(proxyJavaFileDir, interface_);
- Class<?> $Proxy0 = loader.findClass(PROXY_CLASS_NAME);
- return (T)$Proxy0.newInstance();
- }
- private static class MyClassLoader<T> extends ClassLoader {
- private String proxyJavaFileDir;
- private Class<T> interface_;
- public MyClassLoader(String proxyJavaFileDir, Class<T> interface_) {
- this.proxyJavaFileDir = proxyJavaFileDir;
- this.interface_ = interface_;
- }
- @Override
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- File clazzFile = new File(proxyJavaFileDir, name + ".class");
- // 如果字节码文件存在
- if (clazzFile.exists()) {
- // 把字节码文件加载到 VM
- try {
- // 文件流对接 class 文件
- FileInputStream inputStream = new FileInputStream(clazzFile);
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int len;
- while ((len = inputStream.read(buffer)) != -1) {
- baos.write(buffer, 0, len); // 将 buffer 中的内容读取到 baos 中的 buffer
- }
- // 将 buffer 中的字节读到内存加载为 class
- return defineClass(interface_.getPackage().getName() + "." + name, baos.toByteArray(), 0, baos.size());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- return super.findClass(name);
- }
- }
- View Code
有了代理实例对象, 我们就可以利用它进行操作了, 演示结果如下
完整工程地址:, 完整流程图如下
此时的 Proxy 类能创建任何接口的实例, 解决了静态代理存在的代理类泛滥, 多个方法中代理逻辑反复实现的问题; 但有个问题不知道大家注意到:$Proxy0.java 有必要持久化到磁盘吗, 我们能不能直接编译内存中的代理类的字符串源代码, 得到 $Proxy0.class 呢?
代理类源代码不持久化
$Proxy0.java 和 $Proxy0.class 是没必要生成到磁盘的, 我们直接编译内存中的代理类的字符串源代码, 同时直接在内存中加载 $Proxy0.class, 不用写, 读磁盘, 可以提升不少性能
完整工程地址:, 此时的流程图如下
Proxy.java 源代码如下
- package com.lee.proxy;
- import com.itranswarp.compiler.JavaStringCompiler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Type;
- import java.util.Map;
- public class Proxy {
- private static final String ENTER = "\r\n";
- private static final String TAB_STR = " ";
- private static final String PROXY_FILE_NAME = "$Proxy0";
- /**
- * 生成接口实现类的源代码, 并持久化到 java 文件
- * @param interface_
- * @throws Exception
- */
- private static String generateJavaFile(Class<?> interface_) throws Exception {
- StringBuilder proxyJava = new StringBuilder();
- proxyJava.append("package").append(interface_.getPackage().getName()).append(";").append(ENTER).append(ENTER)
- .append("public class").append(PROXY_FILE_NAME).append("implements").append(interface_.getName()).append("{");
- Method[] methods = interface_.getMethods();
- for(Method method : methods) {
- Type returnType = method.getGenericReturnType();
- Type[] paramTypes = method.getGenericParameterTypes();
- proxyJava.append(ENTER).append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
- .append(TAB_STR).append("public").append(returnType.getTypeName()).append("").append(method.getName()).append("(");
- for(int i=0; i<paramTypes.length; i++) {
- if (i != 0) {
- proxyJava.append(",");
- }
- proxyJava.append(paramTypes[i].getTypeName()).append("param").append(i);
- }
- proxyJava.append(") {").append(ENTER)
- .append(TAB_STR).append(TAB_STR)
- .append("System.out.println(\" 数据库操作, 并获取执行结果...\");").append(ENTER); // 真正数据库操作, 会有返回值, 下面的 return 返回应该是此返回值
- if (!"void".equals(returnType.getTypeName())) {
- proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER); // 这里的 "null" 应该是上述中操作数据库后的返回值, 为了演示写成了 null
- }
- proxyJava.append(TAB_STR).append("}").append(ENTER);
- }
- proxyJava .append("}");
- return proxyJava.toString();
- }
- private final static Class<?> compile(String className, String content) throws Exception {
- JavaStringCompiler compiler = new JavaStringCompiler();
- Map<String, byte[]> byteMap = compiler.compile(PROXY_FILE_NAME + ".java", content);
- Class<?> clazz = compiler.loadClass(className, byteMap);
- return clazz;
- }
- public static <T> T newInstance(Class<T> interface_) throws Exception{
- // 1, 生成源代码字符串
- String proxyCodeStr = generateJavaFile(interface_);
- // 2, 字符串编译成 Class 对象
- Class<?> clz = compile(interface_.getPackage().getName() + "." + PROXY_FILE_NAME, proxyCodeStr);
- return (T)clz.newInstance();
- }
- }
- View Code
相比有代理类源代码持久化, 核心的动态代理生成过程不变, 只是减少了. java 和. class 文件的持久化; 其中用到了第三方工具: com.itranswarp.compile(我们也可以拓展 jdk, 实现内存中操作), 完成了字符串在内存中的编译, class 在内存中的加载, 直接用 jdk 的编译工具, 会在磁盘生成 $Proxy0.class
测试结果如下
可以看到, 没有. java 和. class 的持久化
此时就完美了吗? 如果现在有另外一个接口 ISendMessage, 代理逻辑不是
System.out.println("数据库操作, 并获取执行结果...")
我们该怎么办? 针对 ISendMessage 又重新写一个 Proxy? 显然还不够灵活, 说的简单点: 此种代理可以代理任何接口, 但是代理逻辑确是固定死的, 不能自定义, 这样会造成一种代理逻辑会有一个代理工厂(Proxy), 会造成代理工厂的泛滥
代理逻辑接口化, 供用户自定义
既然无代理类源代码持久化中的代理逻辑不能自定义, 那么我们就将它抽出来, 提供代理逻辑接口
完整工程地址:, 流程图与无代理类源代码持久化中一样, 此时代理类的生成过程复杂了不少, 涉及到代理逻辑接口: InvacationHandler 的处理
generateJavaFile(...)方法
- /**
- * 生成接口实现类的源代码
- * @param interface_
- * @throws Exception
- */
- private static String generateJavaFile(Class<?> interface_, InvocationHandler handler) throws Exception {
- StringBuilder proxyJava = new StringBuilder();
- proxyJava.append("package").append(PROXY_PACKAGE_NAME).append(";").append(ENTER).append(ENTER)
- .append("import java.lang.reflect.Method;").append(ENTER).append(ENTER)
- .append("public class").append(PROXY_FILE_NAME).append("implements").append(interface_.getName()).append("{").append(ENTER)
- .append(ENTER).append(TAB_STR).append("private InvocationHandler handler;").append(ENTER).append(ENTER);
- // 代理对象构造方法
- proxyJava.append(TAB_STR).append("public").append(PROXY_FILE_NAME).append("(InvocationHandler handler) {").append(ENTER)
- .append(TAB_STR).append(TAB_STR).append("this.handler = handler;").append(ENTER)
- .append(TAB_STR).append("}").append(ENTER);
- // 接口方法
- Method[] methods = interface_.getMethods();
- for(Method method : methods) {
- String returnTypeName = method.getGenericReturnType().getTypeName();
- Type[] paramTypes = method.getGenericParameterTypes();
- proxyJava.append(ENTER).append(TAB_STR).append("@Override").append(ENTER)
- .append(TAB_STR).append("public").append(returnTypeName).append("").append(method.getName()).append("(");
- List<String> paramList = new ArrayList<>(); // 方法参数值
- List<String> paramTypeList = new ArrayList<>(); // 方法参数类型
- for(int i=0; i<paramTypes.length; i++) {
- if (i != 0) {
- proxyJava.append(",");
- }
- String typeName = paramTypes[i].getTypeName();
- proxyJava.append(typeName).append("param").append(i);
- paramList.add("param" + i);
- paramTypeList.add(typeName+".class");
- }
- proxyJava.append(") {").append(ENTER)
- .append(TAB_STR).append(TAB_STR).append("try {").append(ENTER)
- .append(TAB_STR).append(TAB_STR).append(TAB_STR)
- .append("Method method =").append(interface_.getName()).append(".class.getDeclaredMethod(\"")
- .append(method.getName()).append("\",").append(String.join(",", paramTypeList)).append(");")
- .append(ENTER).append(TAB_STR).append(TAB_STR).append(TAB_STR);
- if (!"void".equals(returnTypeName)) {
- proxyJava.append("return (").append(returnTypeName).append(")");
- }
- proxyJava.append("handler.invoke(this, method, new Object[]{")
- .append(String.join(",", paramList)).append("});").append(ENTER)
- .append(TAB_STR).append(TAB_STR).append("} catch(Exception e) {").append(ENTER)
- .append(TAB_STR).append(TAB_STR).append(TAB_STR).append("e.printStackTrace();").append(ENTER)
- .append(TAB_STR).append(TAB_STR).append("}").append(ENTER);
- if (!"void".equals(returnTypeName)) {
- proxyJava.append(TAB_STR).append(TAB_STR).append("return null;").append(ENTER);
- }
- proxyJava.append(TAB_STR).append("}").append(ENTER);
- }
- proxyJava .append("}");
- // 这里可以将字符串生成 java 文件, 看看源代码对不对
- /*String proxyJavaFileDir = System.getProperty("user.dir") + File.separator + "proxy-none-java-file-plus"
- + String.join(File.separator, new String[]{"","src","main","java",""})
- + PROXY_PACKAGE_NAME.replace(".", File.separator) + File.separator;
- File f = new File(proxyJavaFileDir + PROXY_FILE_NAME + ".java");
- FileWriter fw = new FileWriter(f);
- fw.write(proxyJava.toString());
- fw.flush();
- fw.close();*/
- return proxyJava.toString();
- }
- View Code
测试结果如下
此时各组件之间关系, 调用情况如下
此时 Proxy 就可以完全通用了, 可以生成任何接口的代理对象了, 也可以实现任意的代理逻辑; 至此, 我们完成了一个简易的仿 JDK 实现的动态代理
JDK 的动态代理
我们来看看 JDK 下动态代理的实现, 示例工程:, 测试结果就不展示了, 我们来看看 JDK 下 Proxy.newInstance 方法, 有三个参数
1,Classloader: 类加载器, 我们可以使用自定义的类加载器; 上述手动实现示例中, 直接在 Proxy 写死了;
2,Class<?>[]: 接口类数组, 这个其实很容易理解, 我们应该允许我们自己实现的代理类同时实现多个接口. 我们上述手动实现中只传入一个接口, 是为了简化实现;
3,InvocationHandler: 这个没什么好说的, 与我们的实现一致, 用于自定义代理逻辑
我们来追下源码, 看看 JDK 的动态代理是否与我们的手动实现是否一致
与我们的自定义实现差不多, 利用反射, 逐个接口, 逐个方法进行处理; ProxyClassFactory 负责生成代理类的 Class 对象, 主要有 apply 方法负责, 调用了
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
来生成代理类的 Class;ProxyGenerator 中有个是有静态常量: saveGeneratedFiles, 标识是否持久化代理类的 class 文件, 默认值是 false, 也就是不持久化, 我们可以通过设置 jdk 系统参数, 实现 JDK 的动态代理持久化代理类的 class 文件
CGLIB 代理
对 cglib 不做深入研究了, 只举个使用案例:, 使用方式与 JDK 的动态代理类似, 实现的效果也基本一致, 但是实现原理上还是有差别的
JDK 的动态代理有一个限制, 就是使用动态代理的对象必须实现一个或多个接口, 而 CGLIB 没有这个限制, 具体区别不是本文范畴了, 大家自行去查阅资料
应用场景
长篇大论讲了那么多, 我们却一直没有讲动态代理的作用, 使用动态代理我们可以在不改变源码的情况下, 对目标对象的目标方法进行前置或后置增强处理. 这有点不太符合我们的一条线走到底的编程逻辑, 这种编程模型有一个专业名称叫 AOP, 面向切面编程, 具体案例有如下:
1,spring 的事务, 事务的开启可以作为前置增强, 事务的提交或回滚作为后置增强, 数据库的操作处在两者之前;
2, 日志记录, 我们可以在不改变原有实现的基础上, 对目标对象进行日志的输出, 可以前置处理, 记录参数情况, 也可以后置处理, 记录返回的结果;
3,Web 编程, 传入参数的校验;
4,Web 编程, 权限的控制也可以用 aop 来实现;
只要明白了 AOP, 那么哪些场景能使用动态代理也就比较明了了
总结
1, 示例代码中的 Proxy 是代理工厂, 负责生产代理对象的, 不是代理对象类
2, 手动实现动态代理, 我们分了三版
第一版: 代理类源代码持久化, 为了便于理解, 我们将代理类的 java 文件和 class 文件持久化到了磁盘, 此时解决了静态代理中代理类泛滥的问题, 我们的代理类工厂 (Proxy) 能代理任何接口;
第二版: 代理类源代码不持久化, 代理类的 java 文件和和 class 文件本来就只是临时文件, 将其去掉, 不用读写磁盘, 可以提高效率; 但此时有个问题, 我们的代理逻辑却写死了, 也就说一个代理类工厂只能生产一种代理逻辑的代理类对象, 如果我们有多种代理逻辑, 那么就需要有多个代理类工厂, 显然灵活性不够高, 还有优化空间;
第三版: 代理逻辑接口化, 供用户自定义, 此时代理类工厂就可以代理任何接口, 任何代理逻辑了, 反正代理逻辑是用户自定义传入, 用户想怎么定义就怎么定义;
3, 示例参考的是 mybatis 中 mapper 的生成过程, 虽然只是简单的模拟, 但流程却是一致的, 有兴趣的可以看看我前两篇博客, 结合起来看更好理解
参考
《java 与模式》
10 分钟看懂动态代理设计模式 https://juejin.im/post/5a99048a6fb9a028d5668e62
来源: https://www.cnblogs.com/youzhibing/p/10464274.html