手动模拟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 ...
随机推荐
- CodeForces - 534B-Covered Path+思路
CodeForces - 534B 题意:给定初始和末尾的速度,和最大加速度和总时间,求出走的最长路程: 我一开始以为代码写起来会很繁琐... #include <iostream> #i ...
- CSU 1803 2016 湖南省2016省赛
1803: 2016 Submit Page Summary Time Limit: 5 Sec Memory Limit: 128 Mb Submitted: 1416 ...
- 题解 bzoj 2151 种树
题意 传送门 手写堆大法好啊,题解貌似没有结构体堆的做法,思路有些像配对堆,关于配对堆请自行百度,因为本蒟蒻不会.. 以下是蒟蒻的做法:建立一个大根堆a维护最大价值里面存入它的编号以及价值.听说配对堆 ...
- github 授权登录教程与如何设计第三方授权登录的用户表
需求:在网站上想评论一篇文章,而评论文章是要用户注册与登录的,那么怎么免去这麻烦的步骤呢?答案是通过第三方授权登录.本文讲解的就是 github 授权登录的教程. 效果体验地址:http://biao ...
- 关于重写对象equals方法的问题
1.==和equals的区别 a.基本数据类型使用 == 进行值的比较 b.引用类型使用 == 进行比较时,直接比较的是对象的存储地址,即两个引用是否指向了同一个对象. c.equals方法是基类O ...
- 理解Yarn的执行流程和组件作用
Yarn引入案例 1.学生找院长报到,院长给学生一个学号 2.院长比较忙,继续找主任处理学生事务 3.系主任找院办给学生分配资源(书本) 4.主任找张老师教授java 5.张老师给学生安排座位 6.学 ...
- 基于SSM后台管理系统/人事管理系统
今天给大家分享一个基于SpringMVC+Mybatis+Mysql的后台管理系统,顾名思义,一个系统一般分为前台和后台,前台主要面向用户,而后台主要面向的则是管理员,后台和前台有所不同,后台的业务一 ...
- lambda表达式与匿名内部类与双冒号(::)
lambda表达式在只有一条代码时还可以引用其他方法或构造器并自动调用,可以省略参数传递,代码更加简洁,引用方法的语法需要使用::符号.lambda表达式提供了四种引用方法和构造器的方式: 引用对象的 ...
- tarjan缩点(洛谷P387)
此题解部分借鉴于九野的博客 题目分析 给定一个 \(n\) 个点 \(m\) 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个 ...
- MM52 历史库存及历史金额查询
"表:TABLES:MSEG,LFA1,MARA,EKPO,EKKO,MARD,EKBE,EKET.********************************************* ...