手动模拟JDK动态代理

作者:赐我白日梦

https://www.cnblogs.com/ZhuChangwu/p/11648911.html

为哪些方法代理?

实现自己动态代理,首先需要关注的点就是,代理对象需要为哪些方法代理? 原生JDK的动态代理的实现是往上抽象出一层接口,让目标对象和代理对象都实现这个接口,怎么把接口的信息告诉jdk原生的动态代理呢? 如下代码所示, Proxy.newProxyInstance()方法的第二个参数将接口的信息传递了进去第一个参数的传递进去一个类加载器,在jdk的底层用它对比对象是否是同一个,标准就是 相同对象的类加载器是同一个

ServiceInterface) Proxy.newProxyInstance(service.getClass().getClassLoader()
                , new Class[]{ServiceInterface.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("前置通知");
                method.invoke(finalService,args);
                System.out.println("后置通知");
                return proxy;
            }
        });

我们也效仿它的做法. 代码如下:

public class Test {
    public static void main(String[] args) {
        IndexDao indexDao = new IndexDao();
        Dao  dao =(Dao) ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao));
        assert dao != null;
        System.out.println(dao.say("changwu"));
    }
}

拿到了接口的 Class对象后,通过反射就得知了接口中有哪些方法描述对象 Method, 获取到的所有的方法,这些方法就是我们需要增强的方法

如何将增强的逻辑动态的传递进来呢?

JDK的做法是通过 InvocationHandler的第三个参数完成,他是个接口,里面只有一个抽象方法如下: 可以看到它里面有三个入参,分别是 代理对象,被代理对象的方法,被代理对象的方法的参数

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

当我们使用jdk的动态代理时,就是通过这个重写这个钩子函数,将逻辑动态的传递进去,并且可以选择在适当的地方让目标方法执行

InvocationHandler 接口必须存在必要性1:

为什么不传递进去 Method,而是传递进去 InvocationHandler 对象呢? 很显然,我们的初衷是借助 ProxyUtil工具类完成对代理对象的拼串封装,然后让这个代理对象去执行 method.invoke(), 然而事与愿违,传递进来的Method对象的确可以被 ProxyUtil使用,调用 method.invoke(), 但是我们的代理对象不能使用它,因为代理对象在这个 ProxyUtil 还以一堆等待拼接字符串, ProxyUtil 的作用只能是往代理对象上叠加字符串,却不能直接传递给它一个对象,所以只能传递一个对象进来,然后通过反射获取到这个对象的实例,继而有可能实现method.invoke()

InvocationHandler 接口必须存在必要性2:

通过这个接口的规范,我们可以直接得知回调方法的名字就是 invoke()所以说,在拼接字符串完成对代理对象的拼接时,可以直接写死它

思路

我们需要通过上面的 ProxyUtil.newInstance(Dao.class,new MyInvocationHandlerImpl(indexDao))方法完成如下几件事

  • 根据入参位置的信息,提取我们需要的信息,如包名,方法名,等等
  • 根据我们提取的信息通过字符串的拼接完成一个全新的java的拼接
  • 这个java类就是我们的代理对象
  • 拼接好的java类是一个String字符串,我们将它写入磁盘取名XXX.java
  • 通过 ProxyUtil使用类加载器,将XXX.java读取JVM中,形成Class对象
  • 通过Class对象反射出我们需要的代理对象

ProxyUtil 的实现如下:

public static Object newInstance(Class targetInf, MyInvocationHandler invocationHandler) {

    Method methods[] = targetInf.getDeclaredMethods();
    String line = "\n";
    String tab = "\t";
    String infName = targetInf.getSimpleName();
    String content = "";
    String packageContent = "package com.myproxy;" + line;
    //   导包,全部导入接口层面,换成具体的实现类就会报错
    //
    String importContent = "import " + targetInf.getName() + ";" + line
                           + "import com.changwu.代理技术.模拟jdk实现动态代理.MyInvocationHandler;" + line
                           + "import java.lang.reflect.Method;" + line
                           + "import java.lang.Exception;" + line;

    String clazzFirstLineContent = "public class $Proxy implements " + infName +"{"+ line;
    String filedContent = tab + "private MyInvocationHandler handler;"+ line;
    String constructorContent = tab + "public $Proxy (MyInvocationHandler  handler){" + line
            + tab + tab + "this.handler =handler;"
            + line + tab + "}" + line;
    String methodContent = "";
    // 遍历它的全部方法,接口出现的全部方法进行增强
    for (Method method : methods) {
        String returnTypeName = method.getReturnType().getSimpleName();         method.getReturnType().getSimpleName());

        String methodName = method.getName();
        Class[] parameterTypes = method.getParameterTypes();

        // 参数的.class
        String paramsClass = "";
        for (Class parameterType : parameterTypes) {
            paramsClass+= parameterType.getName()+",";
        }

        String[] split = paramsClass.split(",");

        //方法参数的类型数组 Sting.class String.class
        String argsContent = "";
        String paramsContent = "";
        int flag = 0;
        for (Class arg : parameterTypes) {
            // 获取方法名
            String temp = arg.getSimpleName();
            argsContent += temp + " p" + flag + ",";
            paramsContent += "p" + flag + ",";
            flag++;
        }
        // 去掉方法参数中最后面多出来的,
        if (argsContent.length() > 0) {
            argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
            paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
        }
        methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line
                + tab + tab+"Method method = null;"+line
                + tab + tab+"String [] args0 = null;"+line
                + tab + tab+"Class [] args1= null;"+line

                // invoke入参是Method对象,而不是上面的字符串,所以的得通过反射创建出Method对象
                + tab + tab+"try{"+line
                // 反射得到参数的类型数组
                 + tab + tab + tab + "args0 = \""+paramsClass+"\".split(\",\");"+line
                 + tab + tab + tab + "args1 = new Class[args0.length];"+line
                 + tab + tab + tab + "for (int i=0;i"+line
                 + tab + tab + tab + "   args1[i]=Class.forName(args0[i]);"+line
                 + tab + tab + tab + "}"+line
                // 反射目标方法
                + tab + tab + tab + "method = Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\",args1);"+line
                + tab + tab+"}catch (Exception e){"+line
                + tab + tab+ tab+"e.printStackTrace();"+line
                + tab + tab+"}"+line
                + tab + tab + "return ("+returnTypeName+") this.handler.invoke(method,\"暂时不知道的方法\");" + line; //
                 methodContent+= tab + "}"+line;
    }

    content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";

    File file = new File("d:\\com\\myproxy\\$Proxy.java");
    try {
        if (!file.exists()) {
            file.createNewFile();
        }

        FileWriter fw = new FileWriter(file);
        fw.write(content);
        fw.flush();
        fw.close();

        // 将生成的.java的文件编译成 .class文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
        Iterable units = fileMgr.getJavaFileObjects(file);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        t.call();
        fileMgr.close();

        // 使用类加载器将.class文件加载进jvm
        // 因为产生的.class不在我们的工程当中
        URL[] urls = new URL[]{new URL("file:D:\\\\")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class clazz = urlClassLoader.loadClass("com.myproxy.$Proxy");
        return clazz.getConstructor(MyInvocationHandler.class).newInstance(invocationHandler);
    } catch (Exception e) {
        e.printStackTrace();
    }
       return null;
}
}

运行的效果:

package com.myproxy;
import com.changwu.myproxy.pro.Dao;
import com.changwu.myproxy.pro.MyInvocationHandler;
import java.lang.reflect.Method;
import java.lang.Exception;
public class $Proxy implements Dao{
    private MyInvocationHandler handler;
    public $Proxy (MyInvocationHandler  handler){
        this.handler =handler;
    }
    public String say(String p) {
        Method method = null;
        String [] args0 = null;
        Class [] args1= null;
        try{
            args0 = "java.lang.String,".split(",");
            args1 = new Class[args0.length];
            for (int i=0;i) {
               args1[i]=Class.forName(args0[i]);
            }
            method = Class.forName("com.changwu.myproxy.pro.Dao").getDeclaredMethod("say",args1);
        }catch (Exception e){
            e.printStackTrace();
        }
        return (String) this.handler.invoke(method,"暂时不知道的方法");
    }
}

解读

通过 newInstance() 用户获取到的代理对象就像上面的代理一样,这个过程是在java代码运行时生成的,但是直接看他的结果和静态代理差不错,这时用户再去调用代理对象的say(), 实际上就是在执行用户传递进去的InvocationHandeler里面的invoke方法, 但是亮点是我们把目标方法的描述对象 Method 同时给他传递进去了,让用户可以执行目标方法+增强的逻辑

当通过反射区执行 Method 对象的 invoke() 方法时,指定的哪个对象的当前方法呢? 这个参数其实是我们手动传递进去的代理对象代码如下

public class MyInvocationHandlerImpl implements MyInvocationHandler {
    private Object obj;
    public MyInvocationHandlerImpl(Object obj) {
        this.obj = obj;
    }
    @Override
    public Object invoke(Method method, Object[] args) {
        System.out.println("前置通知");
        try {
            method.invoke(obj,args);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("后置通知");
        return null;
    }
}

长按关注微信公众号,非常感谢;

手动模拟JDK动态代理

Original: https://www.cnblogs.com/wqsbk/p/11669649.html
Author: 汪强胜
Title: 手动模拟JDK动态代理

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/544201/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • HTTP缺点有哪些,如何解决

    前言 大家好,我是蜗牛,在上一篇中,我们介绍了不同版本的HTTP区别和发展背景,这篇文章我们来聊聊HTTP的缺点,HTTP缺点大致总结有以下三点: 通信使用明文(不加密),内容可能…

    Java 2023年6月15日
    0103
  • Spring 基于注解配置bean之简单入门

    Spring 注解配置bean 复习注解相关的知识 啥是注解? 直接是一种特殊的标识符。可在源码或运行阶段起作用。 注解类型 元注解 如 **@Target** 自定义注解 Spr…

    Java 2023年6月7日
    047
  • java接口

    interface 接口名{ //属性 //方法(1.抽象方法 2.默认实现方法 3.静态方法) } class 类名 implements 接口{ 自己的属性; 自己的方法; 必…

    Java 2023年6月5日
    070
  • java对形参操作能否改变实参

    这个问题其实以前就断断续续的纠结过,这次机缘巧合之下稍微深入的理解了这个问题。 这里的问题是:在主方法里创建了N个一般属性,将这些属性传递给其他方法,当 其他方法改变了传递来的形参…

    Java 2023年6月7日
    064
  • 前端浅学之html

    浅学html 基础语法 标签 单标签 无属性 有属性 双标签 无属性 有属性 结构 表示当前是网页 头部信息 页面内容 举例 基础语法 好家伙 标题、水平线 语法 举例 嗯哼 搞笑…

    Java 2023年6月16日
    085
  • [javaweb]监听器统计网页在线人数

    监听器 1.配置监听器 package com.javaweb.controller; import javax.servlet.ServletContext; import ja…

    Java 2023年6月6日
    080
  • CAS 单点登录【2】自定义用户验证

    方案1:CAS默认的JDBC扩展方案: CAS自带了两种简单的通过JDBC方式验证用户的处理器。 这两个处理类位于cas-server-support-jdbc这个扩展工程下。 第…

    Java 2023年5月29日
    072
  • 关系数据库元数据处理类(三) 创建查询元数据公共类

    1 public abstract class BaseMetadata : IMetadata 2 { 3 protected IDbUtility DbUtility; 4 5…

    Java 2023年6月5日
    062
  • 超详细的SpringBoot框架入门教程

    Spring Boot 框架快速入门教程以大量示例讲解了 Spring Boot 在各类情境中的应用,让大家可以跟着老师的思维和代码快速理解并掌握。适用于 Java 开发人员,尤其…

    Java 2023年6月9日
    046
  • Mybatis系列全解(五):全网最全!详解Mybatis的Mapper映射文件

    Mybatis系列全解(五):全网最全!详解Mybatis的Mapper映射文件 Mybatis系列全解(五):全网最全!详解Mybatis的Mapper映射文件 – …

    Java 2023年6月7日
    093
  • 软件工程 毕业设计题目汇总

    软件工程毕业设计 题目汇总 【不断更新中】 微信小程序 校园表白墙微信小程序 【地址:程序地址】 房屋租赁管理系统 【地址:程序地址】 航空售票管理系统 高校会议室管理系统 高校就…

    Java 2023年6月8日
    094
  • AOP代理模式

    代理模式:1.静态代理原理:创建一个代理类实现目标类的接口,在代理类中包含目标类的对象 案例: 1)需要创建一个接口 java;gutter:true; public interf…

    Java 2023年6月13日
    085
  • 集成 Redis & 异步任务 SpringBoot 2.7 .2实战基础

    SpringBoot 2.7 .2实战基础 – 09 – 集成 Redis & 异步任务 1 集成Redis 《docker 安装 MySQL 和 …

    Java 2023年6月16日
    070
  • 常用Linux命令 reboot halt shutdown passwd vlock exit等

    1、重新启动和关闭系统: (1)reboot命令: 选项含义如下: -d :重新启动后,系统不向/var/tmp/wtmp文件中写入记录 -f :强制系统重新启动 -w :仅做测试…

    Java 2023年6月8日
    051
  • 更多的人力投入真的意味着更快的工作吗?

    ——《人月神话》读后感 如果三个工人需要用十个小时的时间去挖一个坑,那么对于一个相同大小的坑,如果现在有六个工人,我们需要多长时间呢?没错,这个问题实在是简单的不能再简单了——五小…

    Java 2023年6月7日
    056
  • 手把手教你使用 Spring Boot 3 开发上线一个前后端分离的生产级系统(二)-数据库设计

    数据库设计规约 数据库设计遵循以下的阿里开发手册(嵩山版)MySQL 数据库规约: 1. 表达是&#x4E…

    Java 2023年6月8日
    099
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球