当前位置: 首页 > 编程笔记 >

手动模拟JDK动态代理的方法

宿淳
2023-03-14
本文向大家介绍手动模拟JDK动态代理的方法,包括了手动模拟JDK动态代理的方法的使用技巧和注意事项,需要的朋友参考一下

为哪些方法代理?

实现自己动态代理,首先需要关注的点就是,代理对象需要为哪些方法代理? 原生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))方法完成如下几件事

  1. 根据入参位置的信息,提取我们需要的信息,如包名,方法名,等等
  2. 根据我们提取的信息通过字符串的拼接完成一个全新的java的拼接
  3. 这个java类就是我们的代理对象
  4. 拼接好的java类是一个String字符串,我们将它写入磁盘取名XXX.java
  5. 通过ProxyUtil使用类加载器,将XXX.java读取JVM中,形成Class对象
  6. 通过Class对象反射出我们需要的代理对象
  7. 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<args0.length;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<args0.length;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;
  }
}

作者: 赐我白日梦

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

以上就是手动模拟JDK动态代理的方法的详细内容,更多关于模拟jdk动态代理的资料请关注小牛知识库其它相关文章!

 类似资料:
  • 本文向大家介绍浅谈Java代理(jdk静态代理、动态代理和cglib动态代理),包括了浅谈Java代理(jdk静态代理、动态代理和cglib动态代理)的使用技巧和注意事项,需要的朋友参考一下 一、代理是Java常用的设计模式,代理类通过调用被代理类的相关方法,并对相关方法进行增强。加入一些非业务性代码,比如事务、日志、报警发邮件等操作。 二、jdk静态代理 1、业务接口 2、业务实现类 3、代理类

  • 本文向大家介绍Java JDK 动态代理的使用方法示例,包括了Java JDK 动态代理的使用方法示例的使用技巧和注意事项,需要的朋友参考一下 本文主要和大家分享介绍了关于Java JDK 动态代理使用的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 前言 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消

  • 本文向大家介绍详解Java JDK动态代理,包括了详解Java JDK动态代理的使用技巧和注意事项,需要的朋友参考一下   今天来看看Java的另一种代理方式——JDK动态代理   我们之前所介绍的代理方式叫静态代理,也就是静态的生成代理对象,而动态代理则是在运行时创建代理对象。动态代理有更强大的拦截请求功能,因为可以获得类的运行时信息,可以根据运行时信息来获得更为强大的执(骚)行(操)力(作)。

  • 本文向大家介绍spring cglib 与 jdk 动态代理,包括了spring cglib 与 jdk 动态代理的使用技巧和注意事项,需要的朋友参考一下 1. 概述 JDK动态代理是利用java反射机制 生成一个实现接口的匿名类, 在调用具体方法前调用InvocationHandler来处理 Cglib动态代理是 利用asm开源包 把被代理类的class文件加载进来 通过修改其字节码生成子类来处

  • 下面是我在测试中尝试做的事情。 TestConfig: 测试:: mockito:通过反射调用参数的mock方法 mockito:是否可以将mock与方法名结合在一起,在when()调用中创建methodCall? 利用Mockito通过反射模拟方法

  • 本文向大家介绍一个简单JDK版动态代理,包括了一个简单JDK版动态代理的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了手动实现的一个简单JDK版动态代理,供大家参考,具体内容如下 一.实现步骤 1.根据目标类的接口类型生成代理类的java文件。 2.编译代理类java文件为.class字节码文件。 3.将编译好的字节码文件加载到jvm中。 4.生成代理类对象并返回。 二.代码实现 1