动态代理

代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位。代理模式从类型上来说,可以分为静态代理和动态代理两种类型。

在解释动态代理之前我们先理解一下静态代理:

首先你要明白静态代理的作用

我们有一个字体提供类,有多种实现(从磁盘,从网络,从系统)

public interface FontProvider {Font getFont(String name);
}public abstract class ProviderFactory {public static FontProvider getFontProvider() {return new FontProviderFromDisk();}
}public class Main() {public static void main(String[] args) {FontProvider fontProvider = ProviderFactory.getFontProvider();Font font = fontProvider.getFont("微软雅黑");......}
}

现在我们希望给他加上一个缓存功能,我们可以用静态代理来完成

public class CachedFontProvider implements FontProvider {private FontProvider fontProvider;private Map<String, Font> cached;public CachedFontProvider(FontProvider fontProvider) {this.fontProvider = fontProvider;}public Font getFont(String name) {Font font = cached.get(name);if (font == null) {font = fontProvider.getFont(name);cached.put(name, font);}return font;}
}/* 对工厂类进行相应修改,代码使用处不必进行任何修改。这也是面向接口编程以及工厂模式的一个好处 */
public abstract class ProviderFactory {public static FontProvider getFontProvider() {return new CachedFontProvider(new FontProviderFromDisk());}
}

当然,我们直接修改FontProviderFromDisk类也可以实现目的,但是我们还有FontProviderFromNet, FontProviderFromSystem等多种实现类,一一修改太过繁琐且易出错。况且将来还可能添加日志,权限检查,异常处理等功能显然用代理类更好一点。

然而今天的重点是:我们都知道牛逼轰轰的Spring AOP的实现的一种方式是使用JDK的动态代理(另一种是cglib),大部分人也会用jdk的动态代理,不过没有研究过jdk的动态代理到底是怎么实现的。今天就来揭开他的神秘面纱;

1. 原理源码剖析

首先我们先来讲一下JDK动态代理的实现原理

1.拿到被代理对象的引用,然后获取他的接口
2.JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口
3.把被代理对象的引用拿到了
4.重新动态生成一个class字节码
5.然后编译

然后先实现一个动态代理

代码很简单了,就是实现
java.lang.reflect.InvocationHandler接口,并使用
java.lang.reflect.Proxy.newProxyInstance()方法生成代理对象

/*** @author mark* @date 2018/3/30*/
public class JdkInvocationHandler implements InvocationHandler {private ProductService target;public Object getInstance(ProductService target){this.target = target;Class clazz = this.target.getClass();// 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");String currentDate  = simpleDateFormat.format(new Date());System.out.println("日期【"+currentDate + "】添加了一款产品");return method.invoke(this.target,args);}
}

被代理接口和实现

/*** 模仿产品Service* @author mark* @date 2018-03-30*/
public interface ProductService {/*** 添加产品* @param productName*/void addProduct(String productName);
}/*** @author mark* @date 2018/3/30*/
public class ProductServiceImpl implements ProductService{public void addProduct(String productName) {System.out.println("正在添加"+productName);}
}

测试类

public class Test {public static void main(String[] args) throws Exception {ProductService productService = new ProductServiceImpl();ProductService proxy = (ProductService) new JdkInvocationHandler().getInstance(productService);proxy.addProduct("iphone");// 这里我们将jdk生成的代理类输出了出来,方便后面分析使用byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()});FileOutputStream os = new FileOutputStream("Proxy0.class");os.write(bytes);os.close();}
}

结果输出

日期【2018-03-30】添加了一款产品
正在添加iphoneProcess finished with exit code 0

上面我们实现动态动态代理的时候输出了代理类的字节码文件,现在来看一下字节码文件反编译过后的内容

import com.gwf.jdkproxy.ProductServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;// 继承了Proxy类
public final class $Proxy0 extends Proxy implements ProductServiceImpl {private static Method m1;private static Method m8;private static Method m2;private static Method m3;private static Method m5;private static Method m4;private static Method m7;private static Method m9;private static Method m0;private static Method m6;public $Proxy0(InvocationHandler var1) throws  {super(var1);}....
..../**
* 这里是代理类实现的被代理对象的接口的相同方法
*/public final void addProduct(String var1) throws  {try {// super.h 对应的是父类的h变量,他就是Proxy.nexInstance方法中的InvocationHandler参数// 所以这里实际上就是使用了我们自己写的InvocationHandler实现类的invoke方法super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final Class getClass() throws  {try {return (Class)super.h.invoke(this, m7, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}....
....
// 在静态构造块中,代理类通过反射获取了被代理类的详细信息,比如各种方法static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m8 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notify");m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("addProduct", Class.forName("java.lang.String"));m5 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE);m4 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE, Integer.TYPE);m7 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("getClass");m9 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notifyAll");m0 = Class.forName("java.lang.Object").getMethod("hashCode");m6 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

补充一下上面代母注释中的super.h

protected InvocationHandler h;protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}// 这个方法是Proxy的newProxyInstance方法,主要就是生成了上面的动态字节码文件
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});}
// 重点看这里,将我们传来的InvocationHandler参数穿给了构造函数return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}}
以上就是jdk动态代理的内部实现过程,最后再次将上面的原理声明一遍,强化记忆

1.拿到被代理对象的引用,然后获取他的接口 (Proxy.getInstance方法)
2.JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口 (上面的反编译文件中实现了同样的接口)
3.把被代理对象的引用拿到了(上面被代理对象中在静态代码块中通过反射获取到的信息,以及我们实现的JdkInvocationHandler中的target)
4.重新动态生成一个class字节码
5.然后编译

2.自己手写一个动态代理

(声明:本代码只用作实例,很多细节没有考虑进去,比如,多接口的代理类,Object类的其他默认方法的代理,为确保原汁原味,一些模板引擎和commons工具类也没有使用;觉得不足的老铁们可以随意完善,记得评论区留言完善方法哦)

我们使用jdk代理的类名和方法名定义,已经执行思路,但是所有的实现都自己来写;

首先先定义出类结构

/*** 自定义类加载器* @author gaowenfeng* @date 2018/3/30*/
public class MyClassLoader extends ClassLoader {/*** 通过类名称加载类字节码文件到JVM中* @param name 类名* @return 类的Class独享* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {return super.findClass(name);}
}
/*** @desc 自己实现的代理类,用来生成字节码文件,并动态加载到JVM中* @author gaowenfeng* @date 2018/3/30*/
public class MyProxy {/*** 生成代理对象* @param loader 类加载器,用于加载被代理类的类文件* @param interfaces 被代理类的接口* @param h 自定义的InvocationHandler接口,用于具体代理方法的执行* @return 返回被代理后的代理对象* @throws IllegalArgumentException*/public static Object newProxyInstance(MyClassLoader loader,Class<?>[] interfaces,MyInvocationHandler h)throws IllegalArgumentException{/*** 1.生成代理类的源代码* 2.将生成的源代码输出到磁盘,保存为.java文件* 3.编译源代码,并生成.java文件* 4.将class文件中的内容,动态加载到JVM中* 5.返回被代理后的代理对象*/return null;}
}
/*** 自定义类加载器* @author gaowenfeng* @date 2018/3/30*/
public class MyClassLoader extends ClassLoader {/*** 通过类名称加载类字节码文件到JVM中* @param name 类名* @return 类的Class独享* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {return super.findClass(name);}
}
/*** @author gaowenfeng* @date 2018/3/30*/
public class CustomInvocationHandler implements MyInvocationHandler {private ProductService target;public Object getInstance(ProductService target){this.target = target;Class clazz = this.target.getClass();// 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3// 这里的MyClassLoader先用new的方式保证编译不报错,后面会修改return MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");String currentDate  = simpleDateFormat.format(new Date());System.out.println("日期【"+currentDate + "】添加了一款产品");return method.invoke(this.target,args);}
}

接下来我们来按照步骤一步一步的完善我们的类

生成代理类的源文件

     * 生成代理类的源代码* @return*/private static String genSesource(Class<?> interfaces){StringBuilder src = new StringBuilder();src.append("package com.gwf.custom;").append(ln).append("import java.lang.reflect.Method;").append(ln).append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append(ln).append("private MyInvocationHandler h;").append(ln).append("public $Proxy0(MyInvocationHandler h){").append(ln).append("this.h=h;").append(ln).append("}").append(ln);for(Method method:interfaces.getMethods()){src.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("() {").append(ln).append("try {").append(ln).append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\");").append(ln).append("this.h.invoke(this, m, new Object[]{});").append(ln).append("}catch (Throwable e){").append(ln).append("e.printStackTrace();").append(ln).append("}").append(ln).append("}").append(ln);}src.append("}");return src.toString();}

2.将源文件保存到本地

// 1.生成代理类的源代码String src = genSesource(interfaces);// 2.将生成的源代码输出到磁盘,保存为.java文件String path = MyProxy.class.getResource("").getPath();File file = new File(path+"$Proxy0.java");FileWriter fw = new FileWriter(file);fw.write(src);fw.close();

3.编译源代码,并生成.java文件

// 3.编译源代码,并生成.java文件// 获取java编译器JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();// 标注java文件管理器,用来获取java字节码文件StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);Iterable iterable = manager.getJavaFileObjects(file);// 创建task,通过java字节码文件将类信息加载到JVM中JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);// 开始执行tasktask.call();// 关闭管理器manager.close();

4.将class文件中的内容,动态加载到JVM中

public class MyClassLoader extends ClassLoader {private String baseDir;public MyClassLoader(){this.baseDir = MyClassLoader.class.getResource("").getPath();}/*** 通过类名称加载类字节码文件到JVM中* @param name 类名* @return 类的Class独享* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 获取类名String className = MyClassLoader.class.getPackage().getName()+"."+name;if(null == baseDir) {throw new ClassNotFoundException();}// 获取类文件File file = new File(baseDir,name+".class");if(!file.exists()){throw new ClassNotFoundException();}// 将类文件转换为字节数组try(FileInputStream in = new FileInputStream(file);ByteArrayOutputStream out = new ByteArrayOutputStream();){byte[] buffer = new byte[1024];int len;while ((len = in.read(buffer))!=-1){out.write(buffer,0,len);}// 调用父类方法生成class实例return defineClass(className,out.toByteArray(),0,out.size());} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}

5.返回被代理后的代理对象

Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);return c.newInstance(h);

最后看一下总体的MyProxy类 的 newProxyInstance方法

public static Object newProxyInstance(MyClassLoader loader,Class<?> interfaces,MyInvocationHandler h)throws IllegalArgumentException{/*** 1.生成代理类的源代码* 2.将生成的源代码输出到磁盘,保存为.java文件* 3.编译源代码,并生成.java文件* 4.将class文件中的内容,动态加载到JVM中* 5.返回被代理后的代理对象*/try {// 1.生成代理类的源代码String src = genSesource(interfaces);// 2.将生成的源代码输出到磁盘,保存为.java文件String path = MyProxy.class.getResource("").getPath();File file = new File(path+"$Proxy0.java");FileWriter fw = new FileWriter(file);fw.write(src);fw.close();// 3.编译源代码,并生成.java文件JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);Iterable iterable = manager.getJavaFileObjects(file);JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);task.call();manager.close();// 4.将class文件中的内容,动态加载到JVM中Class proxyClass = loader.findClass("$Proxy0");// 5.返回被代理后的代理对象Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);return c.newInstance(h);} catch (Exception e) {e.printStackTrace();}return null;}

激动人心的时刻:测试运行

public class CustomClient {public static void main(String[] args){ProductService productService = new ProductServiceImpl();ProductService proxy = (ProductService) new CustomInvocationHandler().getInstance(productService);proxy.addProduct();}
}

运行结果

日期【2018-03-30】添加了一款产品
正在添加iphoneProcess finished with exit code 0

总结:以上通过理解jdk动态代理的原理,自己手写了一个动态代理,里面涉及到的重点主要是代理类字节码的生成(这里采用通过反射强行生成源文件并编译的方法,其实应该可以直接生成字节码文件的,有兴趣的同学可以尝试)和将生成的类动态加载到JVM中(本次试验由于测试,比较简单,直接将类名硬编码到了系统里,正常应该是自动加载),虽然还不完善,但是对于理解原理应该是有很多帮助了,欢迎同学们评论区留言评论给出更好的建议

【干货】JDK动态代理的实现原理以及如何手写一个JDK动态代理相关推荐

  1. 深入了解Vue 2响应式原理,并手写一个简单的Vue

    1. Vue 2的响应式原理 Vue.js 一个核心思想是数据驱动.所谓数据驱动是指视图是由数据驱动生成的,对视图的修改,不会直接操作 DOM,而是通过修改数据.vue.js里面只需要改变数据,Vue ...

  2. 深入Lock锁底层原理实现,手写一个可重入锁

    synchronized与lock lock是一个接口,而synchronized是在JVM层面实现的.synchronized释放锁有两种方式: 获取锁的线程执行完同步代码,释放锁 . 线程执行发生 ...

  3. 【手写系列】纯手写实现JDK动态代理

    前言 在Java领域,动态代理应用非常广泛,特别是流行的Spring/MyBatis等框架.JDK本身是有实现动态代理技术的,不过要求被代理的类必须实现接口,不过cglib对这一不足进行了有效补充.本 ...

  4. Array.isArray,Array.from, Array.entries,Array.incledes的实现原理,分别手写方法

    1.Array.isArray 基本使用 const arr = ["1"]; console.log("isArray:", Array.isArray(ar ...

  5. 深度学习 LSTM长短期记忆网络原理与Pytorch手写数字识别

    深度学习 LSTM长短期记忆网络原理与Pytorch手写数字识别 一.前言 二.网络结构 三.可解释性 四.记忆主线 五.遗忘门 六.输入门 七.输出门 八.手写数字识别实战 8.1 引入依赖库 8. ...

  6. 浅析Nginx中各种锁实现丨Nginx中手写一个线程池丨Nginx中反向代理,正向代理,负载均衡,静态web服务丨C++后端开发

    学会nginx中锁的使用,让你对锁豁然开朗 1. 反向代理,正向代理,负载均衡,静态web服务 2. nginx 中 accept 锁实现 自旋锁 信号量 3. nginx 中 线程池 实现以及详解虚 ...

  7. 手写一个线程池,带你学习ThreadPoolExecutor线程池实现原理

    摘要:从手写线程池开始,逐步的分析这些代码在Java的线程池中是如何实现的. 本文分享自华为云社区<手写线程池,对照学习ThreadPoolExecutor线程池实现原理!>,作者:小傅哥 ...

  8. 手写一个动态代理实现,手写Proxy,手写ClassLoader,手写InvocationHandler

    整个过程中用到了手写类加载器, 手写动态生成java代码 手写编译代码 最后实现动态代理 手写Proxy示例代码: package com.example.demo.proxy.custom;impo ...

  9. spring源码分析01-(前期准备)spring核心原理解析和手写简易spring

    1.本文主要介绍内容 本文会把Spring中核心知识点大概解释下.可以对Spring的底层有一个整体的大致了解.主要内容包括: 手写简易spring框架,帮助更好理解spring. 代码点击链接自取 ...

最新文章

  1. AgileEAS.NET平台开发实例-开发环境配置
  2. my understanding for love
  3. mybatis的mapper.xml文件中含有中文注释时运行出错,mybatis配置优化和别名优化 mybatis配置之映射器说明
  4. 为JavaOne 2014做好准备!
  5. 查看redis aof内存_Redis持久化问题定位与优化技巧
  6. python自带网页解析器_python 之网页解析器
  7. 恋爱必经阶段,过不来就分了
  8. python获取二维矩阵的每一行的第一个非零元素
  9. 二进制 十进制 十六进制
  10. SecureCRT的安装、介绍、简单操作
  11. 面向对象(Python):学习笔记之异常
  12. dosubmit 成功不成功_什么是成功,不放弃!
  13. 电脑有网络,但所有浏览器网页都打不开,是怎么回事?
  14. 客服坐席聊天页面html,WebSocket实现简单客服聊天系统
  15. 氢键H-H的博客目录
  16. SOEM方案 STM32H743 EtherCAT主站 基于STM32H743芯片和SOEM的EtherCAT主站源码
  17. 微信小程序入门---01
  18. 怎么从Chrome浏览器中导出扩展程序为crx文件?
  19. 希尔伯特变换(Hilbert Transform)
  20. 详解滤波电路工作原理及相关参数计算

热门文章

  1. 为啥辣椒会辣得人嘴巴疼?这个问题竟然和今年诺奖有关
  2. LoRa VS NB-IoT,一场物联网时代C位争夺战
  3. 王恩哥院士:信息化发展进程中,科学、基础研究和技术、应用都是关键
  4. 4个重要的量子理论实验综述
  5. 迪士尼研究院等将人造“神经纤维”用于软体机器人,赋予其“本体感知能力”!...
  6. UC伯克利教授Stuart Russell人工智能基础概念与34个误区
  7. 心理所发表关于神经科学研究可信度的评论文章
  8. 政府大数据应用的反思;大数据分析应用常见的困难
  9. PS5 发布,揭秘真假难辨的虚拟和现实
  10. 最快捷的PPT技能提升之路 PPT定制 驼峰设计