最近一直在学习Spring的源码,Spring底层大量使用了动态代理。所以花一些时间对动态代理的知识做一下总结。

  1. 我们自己动手模拟一个动态代理

  2. 对JDK动态代理的源码进行分析

场景:
  1. public interface MyService {
  2. void test01();
  3. void test02(String s);
  4. }
  5. public class MyServiceImpl implements MyService {
  6. @Override
  7. public void test01() {
  8. System.out.println("test01");
  9. }
  10. @Override
  11. public void test02(String s) {
  12. System.out.println(s);
  13. }
  14. }
  15. public class Main {
  16. public static void main(String[] args) {
  17. MyServiceImpl target = new MyServiceImpl();
  18. }
  19. }

我们现在要对target对象进行代理。大家可以想想,我们如何去生成这个代理对象呢?

思路:
分析:
  • 我们先不考虑需要针对target生成一个代理对象,就单纯的生成一个对象来说,我们该怎么办呢?肯定是不能new的,因为我们根本没这个类。

  • 所以为了动态的生成这个对象,我们需要动态的生成一个类,也就是说动态的加载一个类到jvm中

所以我们可以这么做:

  1. 根据我们需要生成一个.java文件
  2. 动态编译成一个.class文件
  3. 拿到这个class文件后,我们通过反射获取一个对象

现在问题来了,我们需要生成的java文件该是什么样子呢?我们可以思考,如果要对这个类做静态代理我们需要怎么做?

  1. package com.dmz.proxy;
  2. import com.dmz.proxy.target.MyService;
  3. /**
  4. * 静态代理
  5. */
  6. public class StaticProxy implements MyService {
  7. private MyService target;
  8. public StaticProxy(MyService target) {
  9. this.target = target;
  10. }
  11. @Override
  12. public void test01() {
  13. System.out.println("proxy print log for test01");
  14. target.test01();
  15. }
  16. @Override
  17. public void test02(String s) {
  18. System.out.println("proxy print log for test02");
  19. target.test02(s);
  20. }
  21. }

上面就是静态代理的代码,如果我们可以动态的生成这样的一个.java文件,然后调用jdk的方法进行编译,是不是就解决问题了呢?

实践:

所以我们现在需要

  1. 拼接字符串,将上面的代码以字符串的形式拼接出来并写入到磁盘文件上,并命名为xxxx.java文件

  2. 编译.java文件,生成.class文件

  3. 加载这个class文件到JVM内存中(实际上就是方法区中),得到一个class对象

  4. 调用反射方法,class.newInstanc(…)

代码如下:

  1. public class ProxyUtil {
  2. /**
  3. * @param target 目标对象
  4. * @return 代理对象
  5. */
  6. public static Object newInstance(Object target) {
  7. Object proxy = null;
  8. // 开始拼接字符串
  9. Class targetInf = target.getClass().getInterfaces()[0];
  10. Method[] methods = targetInf.getDeclaredMethods();
  11. String line = System.lineSeparator();
  12. String tab = "\t";
  13. String infName = targetInf.getSimpleName();
  14. String content = "";
  15. String packageContent = "package com.dmz.proxy;" + line;
  16. String importContent = "import " + targetInf.getName() + ";" + line;
  17. String clazzFirstLineContent = "public class $Proxy implements " + infName + "{" + line;
  18. String filedContent = tab + "private " + infName + " target;" + line;
  19. String constructorContent = tab + "public $Proxy (" + infName + " target){" + line
  20. + tab + tab + "this.target =target;"
  21. + line + tab + "}" + line;
  22. String methodContent = "";
  23. for (Method method : methods) {
  24. String returnTypeName = method.getReturnType().getSimpleName();
  25. String methodName = method.getName();
  26. // Sting.class String.class
  27. Class args[] = method.getParameterTypes();
  28. String argsContent = "";
  29. String paramsContent = "";
  30. int flag = 0;
  31. for (Class arg : args) {
  32. String temp = arg.getSimpleName();
  33. //String
  34. //String p0,Sting p1,
  35. argsContent += temp + " p" + flag + ",";
  36. paramsContent += "p" + flag + ",";
  37. flag++;
  38. }
  39. if (argsContent.length() > 0) {
  40. argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
  41. paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
  42. }
  43. methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line
  44. + tab + tab + "System.out.println(\"proxy print log for " + methodName + "\");" + line
  45. + tab + tab + "target." + methodName + "(" + paramsContent + ");" + line
  46. + tab + "}" + line;
  47. }
  48. content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";
  49. //字符串拼接结束
  50. // 开始生成.java文件
  51. File file = new File("g:\\com\\dmz\\proxy\\$Proxy.java");
  52. try {
  53. if (!file.exists()) {
  54. file.createNewFile();
  55. }
  56. FileWriter fw = new FileWriter(file);
  57. fw.write(content);
  58. fw.flush();
  59. fw.close();
  60. // .java文件生成结束
  61. // 开始进行编译
  62. JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
  63. StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
  64. Iterable units = fileMgr.getJavaFileObjects(file);
  65. JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
  66. t.call();
  67. fileMgr.close();
  68. // 编译结束,生成.class文件
  69. // 从G盘中加载class文件
  70. URL[] urls = new URL[]{new URL("file:G:\\\\")};
  71. URLClassLoader urlClassLoader = new URLClassLoader(urls);
  72. // 加载
  73. Class clazz = urlClassLoader.loadClass("com.dmz.proxy.$Proxy");
  74. // 加载结束
  75. // 构造代理对象
  76. Constructor constructor = clazz.getConstructor(targetInf);
  77. proxy = constructor.newInstance(target);
  78. } catch (Exception e) {
  79. e.printStackTrace();
  80. }
  81. return proxy;
  82. }
  83. }

我们调用这个方法:

  1. public class Main {
  2. public static void main(String[] args) {
  3. MyServiceImpl target = new MyServiceImpl();
  4. MyService o = ((MyService) ProxyUtil.newInstance(target));
  5. o.test01();
  6. o.test02("test02");
  7. }
  8. }

会在我们的G盘中生成文件:

打开.java文件可以看到如下内容:

同时控制台会正常打印:

这样,我们就完成了一个简单的代理。

动态代理学习(一)自己动手模拟JDK动态代理的更多相关文章

  1. 【原创】分布式之缓存击穿 【原创】自己动手实现静态资源服务器 【原创】自己动手实现JDK动态代理

    [原创]分布式之缓存击穿   什么是缓存击穿 在谈论缓存击穿之前,我们先来回忆下从缓存中加载数据的逻辑,如下图所示 因此,如果黑客每次故意查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查 ...

  2. 手动模拟JDK动态代理

    为哪些方法代理? 实现自己动态代理,首先需要关注的点就是,代理对象需要为哪些方法代理? 原生JDK的动态代理的实现是往上抽象出一层接口,让目标对象和代理对象都实现这个接口,怎么把接口的信息告诉jdk原 ...

  3. spring源码学习【准备】之jdk动态代理和cglib动态代理的区别和性能

    一:区别:---->JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了.--->JDK的动态代理机制只能代理实现了接口的类,而不能 ...

  4. 【原创】自己动手实现JDK动态代理

    项目结构如下图所示,maven项目 1.JDK动态代理 先来一段jdk动态代理的demo, 首先创建一个接口,Person package bean; public interface Person ...

  5. 自己动手实现JDK动态代理

    出自:作者:孤独烟  http://rjzheng.cnblogs.com/ ------------------------------------------------------------- ...

  6. 代理模式精讲(手写JDK动态代理)

    代理模式是一种架构型模式,表现出来就是一个类代表另一个类的功能,一般用在想对访问一个类的时候做一些控制,同时又不想影响正常的业务,这种代理模式在现实的生活中应用的也非常的广泛,我用穷举法给举几个好理解 ...

  7. spring源码学习之【准备】jdk动态代理例子

    一:被代理的对象所要实现的接口 package com.yeepay.testpoxy; import java.util.Map; /** * 被动态代理的接口 * @author shangxia ...

  8. 面试官:你说你懂动态代理,那你知道为什么JDK中的代理类都要继承Proxy吗?

    之前我已经写过了关于动态代理的两篇文章,本来以为这块应该没啥问题,没想到今天又被难住了- 太难了!!! 之前文章的链接: 动态代理学习(一)自己动手模拟JDK动态代理. 动态代理学习(二)JDK动态代 ...

  9. Spring中的JDK动态代理

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

随机推荐

  1. Xshell远程连接Linux系统

    一般来说我们连接Linux,会使用到一些远程连接工具 比如:Xshell和Xftp Xshell:远程连接linux系统 Xftp:远程在Linux系统中上传或下载文件 Xshell和Xftp百度云链 ...

  2. Linux学习笔记(六)压缩和解压缩命令

    压缩和解压缩命令 zip unzip gzip gunzip bzip2 bunzip2 tar zip (.zip格式的压缩文件) 英文原意:package and compress (archiv ...

  3. Java中的匿名对象代码实例

    /* 匿名对象:就是没有名字的对象. 匿名对象的应用场景: A:调用场景,仅仅只调用一次的时候. 注意:调用多次的时候,不合适. 那么,这种匿名调用有什么好处吗? 有,匿名对象调用完毕就是垃圾.可以被 ...

  4. orcale 多列转一行显示

    强大的数据库有个自带函数wm_concat() wm_concat()这个函数放的是需要汇总的列 select wm_concat(name) name  from tablename

  5. 理解java容器底层原理--手动实现HashMap

    HashMap结构 HashMap的底层是数组+链表,百度百科找了张图: 先写个链表节点的类 package com.xzlf.collection2; public class Node { int ...

  6. MySQL为某字段加前缀、后缀

    在开发过程中,可能会遇到加前缀或者后缀的情况.比如为视频添加路径时,如果手动加起来肯定慢,而且比较不符合程序员的特点,我们就应该能让程序跑就不会手动加. 使用UPDATE sql 语句:update ...

  7. Python数据分析入门与实践 学习

    pandas是一个Python语言的软件包,在我们使用Python语言进行机器学习编程的时候,这是一个非常常用的基础编程库.本文是对它的一个入门教程.pandas提供了快速,灵活和富有表现力的数据结构 ...

  8. Qt5 escape spaces in path

    There are two possible ways. You can either use escaped quotes (inserting the string between quotes) ...

  9. C#多线程(16):手把手教你撸一个工作流

    目录 前言 节点 Then Parallel Schedule Delay 试用一下 顺序节点 并行任务 编写工作流 接口构建器 工作流构建器 依赖注入 实现工作流解析 前言 前面学习了很多多线程和任 ...

  10. 已有项目接入git远程仓库

    1.项目根目录初始化git仓库 git init 2.将本地项目与远程仓库关联(首先得在远程创建一个代码仓库) git remote add origin 远程仓库地址 诺,仓库地址就是这个玩意 3. ...