手动模拟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))方法完成如下几件事
- 根据入参位置的信息,提取我们需要的信息,如包名,方法名,等等
- 根据我们提取的信息通过字符串的拼接完成一个全新的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动态代理的更多相关文章
- 动态代理学习(一)自己动手模拟JDK动态代理
最近一直在学习Spring的源码,Spring底层大量使用了动态代理.所以花一些时间对动态代理的知识做一下总结. 我们自己动手模拟一个动态代理 对JDK动态代理的源码进行分析 文章目录 场景: 思路: ...
- 对JDK动态代理的模拟实现
对JDK动态代理的模拟 动态代理在JDK中的实现: IProducer proxyProduec = (IProducer)Proxy.newProxyInstance(producer.getCla ...
- Spring中的JDK动态代理
Spring中的JDK动态代理 在JDK1.3以后提供了动态代理的技术,允许开发者在运行期创建接口的代理实例.在Sun刚推出动态代理时,还很难想象它有多大的实际用途,现在动态代理是实现AOP的绝好底层 ...
- 模拟实现jdk动态代理
实现步骤 1.生成代理类的源代码 2.将源代码保存到磁盘 3.使用JavaCompiler编译源代码生成.class字节码文件 4.使用JavaCompiler编译源代码生成.class字节码文件 5 ...
- 从静态代理,jdk动态代理到cglib动态代理-一文搞懂代理模式
从代理模式到动态代理 代理模式是一种理论上非常简单,但是各种地方的实现往往却非常复杂.本文将从代理模式的基本概念出发,探讨代理模式在java领域的应用与实现.读完本文你将get到以下几点: 为什么需要 ...
- AOP学习心得&jdk动态代理与cglib比较
什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入 ...
- Spring AOP详解 、 JDK动态代理、CGLib动态代理
AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码 ...
- JDK动态代理与Cglib库
JDK动态代理 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在 ...
- 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理
Spring AOP详解 . JDK动态代理.CGLib动态代理 原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...
随机推荐
- lightoj 1095 - Arrange the Numbers(dp+组合数)
题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1095 题解:其实是一道简单的组合数只要推导一下错排就行了.在这里就推导一下错排 ...
- 用mmap做I/O
1. Introduction mmap是Linux中一个相对复杂的函数,仅函数的参数就有6个.但是他也是一个十分强大的函数,在文件I/O,进程间通信,和malloc函数的实现中皆有他的身影 ...
- Java集合中List、Set以及Map
概述: List , Set, Map都是接口:List , Set继承至Collection接口,Map为独立接口 Set下有HashSet,LinkedHashSet,TreeSet List下有 ...
- hive正则表达式的用法
regexp_replace用法 1. 截取字符串中的汉字部分: 举个栗子:select regexp_replace('七夕节comming!来啦','([^\\u4E00-\\u9FA5]+)' ...
- 【4】Logistic回归
前言 logistic回归的主要思想:根据现有数据对分类边界建立回归公式,以此进行分类 所谓logistic,无非就是True or False两种判断,表明了这其实是一个二分类问题 我们又知道回归就 ...
- 获取不到jdbc.driver的值解决办法
我存在的问题是: 1.先检查自己是否出错 ①首先想到mysql版本和驱动版本之间的冲突问题,我的mysql是5.5.56,驱动用的5.1.32,上网查了一下可以用,但还是尝试换了一个版本的驱动,还是出 ...
- Docker详解(二)
目录 1.Docker常用命令 1.1 镜像命令 1.2 容器命令 1.2.1 常用的容器命令 1.2.2 重要的容器命令 序言:上一章我们初步介绍了一下Docker的概念,那么这次我们着手于Dock ...
- Python的6种运算符(日记)
学习了许久的Python,我单独总结出了Python中比较常见的6种运算符,感觉略有不全,希望大伙可以一起讨论与研究Python! 一.算术运算符 加 减 - 乘 * 除 / 取余 % 取整 // 异 ...
- 让Jenkins执行GitHub上的pipeline脚本
本文是<Jenkins流水线(pipeline)实战>系列的第二篇,上一篇搭建好了Jenkins环境并执行了一个简单的pipeline任务,当时我们直接在Jenkins网页上编写pipel ...
- Redis删除集群以及重新启动集群
有时候我们搭建完集群以后,对集群进行了一些错误的操作,导致集群出现了不可预料的问题,这时候想要删除集群重新启动一个原始的集群,那么如何删除原来旧的集群呢? 1.关闭所有开启的Redis节点 kill ...