为哪些方法代理?

实现自己动态代理,首先需要关注的点就是,代理对象需要为哪些方法代理? 原生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<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;
}
}

手动模拟JDK动态代理的更多相关文章

  1. 动态代理学习(一)自己动手模拟JDK动态代理

    最近一直在学习Spring的源码,Spring底层大量使用了动态代理.所以花一些时间对动态代理的知识做一下总结. 我们自己动手模拟一个动态代理 对JDK动态代理的源码进行分析 文章目录 场景: 思路: ...

  2. 对JDK动态代理的模拟实现

    对JDK动态代理的模拟 动态代理在JDK中的实现: IProducer proxyProduec = (IProducer)Proxy.newProxyInstance(producer.getCla ...

  3. Spring中的JDK动态代理

    Spring中的JDK动态代理 在JDK1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例.在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在动态代理是实现AOP的绝好底层 ...

  4. 模拟实现jdk动态代理

    实现步骤 1.生成代理类的源代码 2.将源代码保存到磁盘 3.使用JavaCompiler编译源代码生成.class字节码文件 4.使用JavaCompiler编译源代码生成.class字节码文件 5 ...

  5. 从静态代理,jdk动态代理到cglib动态代理-一文搞懂代理模式

    从代理模式到动态代理 代理模式是一种理论上非常简单,但是各种地方的实现往往却非常复杂.本文将从代理模式的基本概念出发,探讨代理模式在java领域的应用与实现.读完本文你将get到以下几点: 为什么需要 ...

  6. AOP学习心得&jdk动态代理与cglib比较

    什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...

  7. Spring AOP详解 、 JDK动态代理、CGLib动态代理

    AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码 ...

  8. JDK动态代理与Cglib库

    JDK动态代理 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在 ...

  9. 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理

    Spring AOP详解 . JDK动态代理.CGLib动态代理  原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...

随机推荐

  1. bzoj 1085骑士精神

    bzoj 1085骑士精神 在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士,且有一个空位.在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐标相差为1,纵坐标相差为2或者横坐标相差为2 ...

  2. ZOJ-1610 Count the Colors ( 线段树 )

    题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1610 Description Painting some co ...

  3. 矩阵快速幂 hud 1575 Tr A 模板 *

    Tr A Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  4. Ubuntu开机出现grub指令,无法正常开机

    问题 最近开机出现了如下的界面: 分析问题 首先看看GNU GRUB是什么东东?干什么用的? GNU GRUB是多重引导加载程序.通俗点说,它就是用来一个可以让你选择运行什么操作系统的程序. 在你开机 ...

  5. JS数组方法整理(附使用示例)

    整理目前所用过的数组方法,学习了新增的es6方法. 1.arr.push() 从后面添加元素,返回值为添加完后的数组的长度 let arr = [1,2, is 8.3,4,5] console.lo ...

  6. 还不会用 K8s 集群控制器?那你会用冰箱吗?(多图详解)

    作者 | 阿里云售后技术专家 声东 导读:当我们尝试去理解 K8s 集群工作原理的时候,控制器(Controller)肯定是一个难点.这是因为控制器有很多,具体实现大相径庭:且控制器的实现用到了一些较 ...

  7. Netty源码分析 (七)----- read过程 源码分析

    在上一篇文章中,我们分析了processSelectedKey这个方法中的accept过程,本文将分析一下work线程中的read过程. private static void processSele ...

  8. Storm 系列(三)—— Storm 单机版本环境搭建

    1. 安装环境要求 you need to install Storm's dependencies on Nimbus and the worker machines. These are: Jav ...

  9. Hive入门--2.分区表 外部分区表 关联查询

    1.查看mysql中metastore数据存储结构 Metastore中只保存了表的描述信息(名字,列,类型,对应目录) 使用SQLYog连接itcast05 的mysql数据库  查看hive数据库 ...

  10. 为什么Kubernetes使用Pod作为最小调度单元

    一.Pod说明 Pod只是一个逻辑概念,一个原子调度单位,其优势在于 可以统一调度一组容器到指定的node上 共享资源,Pod的容器可以使用localhost进行通信,使用volume进行文件共享.使 ...