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

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

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

场景:
public interface MyService {
void test01();
void test02(String s);
} public class MyServiceImpl implements MyService { @Override
public void test01() {
System.out.println("test01");
} @Override
public void test02(String s) {
System.out.println(s);
}
} public class Main {
public static void main(String[] args) {
MyServiceImpl target = new MyServiceImpl();
}
}

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

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

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

所以我们可以这么做:

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

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

package com.dmz.proxy;

import com.dmz.proxy.target.MyService;

/**
* 静态代理
*/
public class StaticProxy implements MyService { private MyService target; public StaticProxy(MyService target) {
this.target = target;
} @Override
public void test01() {
System.out.println("proxy print log for test01");
target.test01();
} @Override
public void test02(String s) {
System.out.println("proxy print log for test02");
target.test02(s);
}
}

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

实践:

所以我们现在需要

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

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

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

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

代码如下:

public class ProxyUtil {
/**
* @param target 目标对象
* @return 代理对象
*/
public static Object newInstance(Object target) {
Object proxy = null;
// 开始拼接字符串
Class targetInf = target.getClass().getInterfaces()[0];
Method[] methods = targetInf.getDeclaredMethods();
String line = System.lineSeparator();
String tab = "\t";
String infName = targetInf.getSimpleName();
String content = "";
String packageContent = "package com.dmz.proxy;" + line;
String importContent = "import " + targetInf.getName() + ";" + line;
String clazzFirstLineContent = "public class $Proxy implements " + infName + "{" + line;
String filedContent = tab + "private " + infName + " target;" + line;
String constructorContent = tab + "public $Proxy (" + infName + " target){" + line
+ tab + tab + "this.target =target;"
+ line + tab + "}" + line;
String methodContent = "";
for (Method method : methods) {
String returnTypeName = method.getReturnType().getSimpleName();
String methodName = method.getName();
// Sting.class String.class
Class args[] = method.getParameterTypes();
String argsContent = "";
String paramsContent = "";
int flag = 0;
for (Class arg : args) {
String temp = arg.getSimpleName();
//String
//String p0,Sting p1,
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 + "System.out.println(\"proxy print log for " + methodName + "\");" + line
+ tab + tab + "target." + methodName + "(" + paramsContent + ");" + line
+ tab + "}" + line; } content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";
//字符串拼接结束 // 开始生成.java文件
File file = new File("g:\\com\\dmz\\proxy\\$Proxy.java");
try {
if (!file.exists()) {
file.createNewFile();
}
FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close();
// .java文件生成结束 // 开始进行编译
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文件 // 从G盘中加载class文件
URL[] urls = new URL[]{new URL("file:G:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
// 加载
Class clazz = urlClassLoader.loadClass("com.dmz.proxy.$Proxy");
// 加载结束 // 构造代理对象
Constructor constructor = clazz.getConstructor(targetInf);
proxy = constructor.newInstance(target); } catch (Exception e) {
e.printStackTrace();
}
return proxy;
}
}

我们调用这个方法:

public class Main {
public static void main(String[] args) {
MyServiceImpl target = new MyServiceImpl();
MyService o = ((MyService) ProxyUtil.newInstance(target));
o.test01();
o.test02("test02");
}
}

会在我们的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. Weblogic-SSRF 漏洞复现

    0x01 环境搭建 我这里使用的是vulhub,它几乎包含了所有的漏洞环境.(建议安装在ubuntu上) 有需要的小伙伴来企鹅群自取. 安装好vulhub之后需要cd 到weblogic ssrf 目 ...

  2. 【半监督学习】MixMatch、UDA、ReMixMatch、FixMatch

    半监督学习(Semi-Supervised Learning,SSL)的 SOTA 一次次被 Google 刷新,从 MixMatch 开始,到同期的 UDA.ReMixMatch,再到 2020 年 ...

  3. 聊聊Disruptor 和 Aeron 这两个开源库

    Disruptor The best way to understand what the Disruptor is, is to compare it to something well under ...

  4. 第九节:os、sys、json、pickle、shelve模块

    OS模块: os.getcwd()获取当前路径os.chdir()改变目录os.curdir返回当前目录os.pardir()父目录os.makedirs('a/b/c')创建多层目录os.remov ...

  5. 【three.js 第一课】创建场景,显示几何体

    <!DOCTYPE html> <html> <head> <title>demo1</title> </head> <s ...

  6. IE各版本CSS Hack(兼容性处理)语法速查表

    为了兼容IE各个版本,需要在CSS中添加额外的代码,比如以前常用的_width.之所以工作,是因为浏览器会忽略不能解析的样式规则,因此举个例子来说,把_width写在width下面,对于非IE浏览器会 ...

  7. stand up meeting 12/11/2015

    part 组员 今日工作 工作耗时/h 明日计划 工作耗时/h UI 冯晓云 完成单词释义热度排序:允许用户自主添加释义:完成了button位置的修正(finally)和弹窗的美化:     6 tr ...

  8. 【翻译】TensorFlow卷积神经网络识别CIFAR 10Convolutional Neural Network (CNN)| CIFAR 10 TensorFlow

    原网址:https://data-flair.training/blogs/cnn-tensorflow-cifar-10/ by DataFlair Team · Published May 21, ...

  9. Java同步方法:synchronized到底锁住了谁?

    目录 前言 同步方法 类的成员方法 类的静态方法 同步代码块 总结 其他同步方法 参考资料 前言 相信不少同学在上完Java课后,对于线程同步部分的实战,都会感到不知其然. 比如上课做实验的时候,按着 ...

  10. Windows系统安装最新版本RabbitMQ3.8.3及报错解决

    今天想安装下RabbitMQ写几个用例看下,发现最新的安装包有些问题,不能直接安装使用,遇到一些问题,记录一下解决办法. 下载安装包 因为RabbitMQ是Erlang编写,安装时,需要先安装Erla ...