动态代理是什么

首先说下代理模式,代理模式是常见的一种java设计模式,特征是代理类委托类实现了同样的接口,代理类主要负责为委托类预处理、过滤、转发,以及事后处理等。代理类与委托类之间通常会存在关联关系,一个代理类的实例与它的委托类的实例是关联的。代理类的实例本身是并不真正关心被调用方法的内部逻辑,而是会通过内部访问调用 委托类的实例真正实现了的方法,来为调用者提供服务。

有代理的话,在访问实际对象时,是通过代理实例来访问、调用委托类方法的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

动态代理对比静态代理,最大的特点是代理类是在程序运行时生成的,并非在编译期生成,能做的事情也多了,自然风险也高了。

动态代理最简单的用法

用一个比较接近生活的例子:中午,饿了的室友 委托 持家有道的你 去点外卖

Hungry.java :接口

  1. public interface Hungry {
  2. void callLunch();
  3. }

Roommate.java :Hungry接口的实现类,也就是委托类

  1. public class Roommate implements Hungry{
  2. private String name;
  3. public Roommate(String name) {
  4. this.name = name;
  5. }
  6. @Override
  7. public void callLunch() {
  8. System.out.println("好饿,今天午饭点外卖吧");
  9. }
  10. }
  1. public class RoommateInvocationHandler<T> implements InvocationHandler {
  2. private T rommate;
  3. public RoommateInvocationHandler(T roommate){
  4. this.rommate = roommate;
  5. }
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. System.out.println("下单前,我先帮你看下有没有平台优惠券吧");
  9. Object result = method.invoke(rommate , args);
  10. return result;
  11. }
  12. }

InvocationHandler是一个接口,由代理实例内部的invocation handler实现的接口。每个代理实例都有一个关联的invocation handler。当代理实例上调用方法时,method.invoke(baseImpl, args),此方法将被编码并织入到代理实例内部的 invocation handler实现的invoke方法中。

利用 Proxy 的方式实现动态代理,调用 委托类接口 的方法,完成午餐点外卖这个操作

  1. public static void main(String[] args) {
  2. Roommate roommate = new Roommate("zhangsan");
  3. Hungry proxyInstance = (Hungry) Proxy.newProxyInstance(
  4. roommate.getClass().getClassLoader(),
  5. roommate.getClass().getInterfaces(),
  6. new RoommateInvocationHandler<Roommate>(roommate)
  7. );
  8. proxyInstance.callLunch();
  9. }
  10. //输出结果
  11. 下单前,我先帮你看下有没有平台优惠券吧
  12. 好饿,今天午饭点外卖吧

代理实例proxyInstance的类型是Hungry,所以只能调用Hungry里规定的方法。Roommate作为接口实现类,不是来自接口的其他的方法,是无法通过动态代理调用的。

可以看到代理实例在调用委托类实现的方法时,可以很方便地在调用方法的前后执行一些操作,在示例代码中则是在调用方法前简单输出了一行: System.out.println("下单前,我先帮你看下有没有平台优惠券吧"),还可以有其他用途,例如记录这个方法的耗时时间,对方法的参数或者返回结果进行修改等等。这也是Spring,Dagger进行AOP编程的原理。

那为什么继承InvocationHandler接口和持有委托类引用的RoommateInvocationHandler调用来自Hungry接口的callLunch()方法时可以调用到委托类对callLunch()的逻辑实现呢,看看它的背后原理:

动态代理的实现原理

Proxy.newProxyInstance() 入手,逐步分析 InvocationHandler 如何建立代理实例和委托实例的关联:

  1. public static Object newProxyInstance(ClassLoader loader , Class<?>[] interfaces,
  2. InvocationHandler h) throws IllegalArgumentException {
  3. //InvocationHandler必须非空,说明是个重要角色
  4. Objects.requireNonNull(h);
  5. //获取委托类的接口
  6. final Class<?>[] intfs = interfaces.clone();
  7. final SecurityManager sm = System.getSecurityManager();
  8. if (sm != null) {
  9. checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
  10. }
  11. /*
  12. * Look up or generate the designated proxy class.
  13. * 核心:通过类加载器和委托类接口,在内存中查找出或者生成指定的代理类
  14. */
  15. Class<?> cl = getProxyClass0(loader, intfs);
  16. /*
  17. * Invoke its constructor with the designated invocation handler.
  18. * 利用指定的invocation handler调用它的构造器方法,构建代理类的实例返回
  19. */
  20. try {
  21. if (sm != null) {
  22. checkNewProxyPermission(Reflection.getCallerClass(), cl);
  23. }
  24. final Constructor<?> cons = cl.getConstructor(constructorParams);
  25. final InvocationHandler ih = h;
  26. if (!Modifier.isPublic(cl.getModifiers())) {
  27. AccessController.doPrivileged(new PrivilegedAction<Void>() {
  28. public Void run() {
  29. cons.setAccessible(true);
  30. return null;
  31. }
  32. });
  33. }
  34. return cons.newInstance(new Object[]{h});
  35. } catch (IllegalAccessException|InstantiationException e) {
  36. throw new InternalError(e.toString(), e);
  37. } catch (InvocationTargetException e) {
  38. Throwable t = e.getCause();
  39. if (t instanceof RuntimeException) {
  40. throw (RuntimeException) t;
  41. } else {
  42. throw new InternalError(t.toString(), t);
  43. }
  44. } catch (NoSuchMethodException e) {
  45. throw new InternalError(e.toString(), e);
  46. }
  47. }

来到这一步好像就停下了,那么接下来探究 cl 这个实例创建过程发生了什么:

在上面示例代码main函数的后面接着补充。利用ProxyGenerator.generateProxyClass生成这个动态生成的类文件,写入了指定路径的class文件内

“$Proxy0” 是 代理类在系统内部的编号,在示例代码只生成了一个代理类所以编号是 $Proxy0 。

  1. byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",Roommate.class.getInterfaces());
  2. String filePath = "C:\\Users\\ODM\\Desktop\\RoommateProxy.class";
  3. try(FileOutputStream fos = new FileOutputStream(filePath)) {
  4. fos.write(classFile);
  5. fos.flush();
  6. }catch (IOException e){
  7. e.printStackTrace();
  8. System.out.println("error:写入文件");
  9. }

使用反编译工具,我这里用的是jd-gui反编译,这个$Proxy0类,实现了Proxy类,继承了和委托类相同的接口

  1. import java.lang.reflect.InvocationHandler;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Proxy;
  4. import java.lang.reflect.UndeclaredThrowableException;
  5. import proxy_test.Hungry;
  6. public final class $Proxy0 extends Proxy implements Hungry{
  7. private static Method m1;
  8. private static Method m3; //由下方静态代码块得知,m3代表callLunch()这一个方法
  9. private static Method m2;
  10. private static Method m0;
  11. /*
  12. * 父类Proxy的构造器,其中 h 属性为 InvocationHandler引用
  13. * protected Proxy(InvocationHandler h) {
  14. Objects.requireNonNull(h);
  15. this.h = h;
  16. }
  17. */
  18. public $Proxy0(InvocationHandler paramInvocationHandler) throws {
  19. super(paramInvocationHandler);
  20. }
  21. //关键!可供外界调用,方法名与委托类实现接口的方法相同,利用 InvocationHandler调用invoke
  22. public final void callLunch() throws {
  23. try{
  24. this.h.invoke(this, m3, null);
  25. return;
  26. }
  27. catch (Error|RuntimeException localError){
  28. throw localError;
  29. }
  30. catch (Throwable localThrowable){
  31. throw new UndeclaredThrowableException(localThrowable);
  32. }
  33. }
  34. public final boolean equals(Object paramObject) throws {}
  35. public final String toString() throws {...}
  36. public final int hashCode() throws {...}
  37. static{
  38. try{
  39. m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
  40. m3 = Class.forName("proxy_test.Hungry").getMethod("callLunch", new Class[0]);
  41. m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
  42. m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
  43. return;
  44. }
  45. catch (NoSuchMethodException localNoSuchMethodException){
  46. throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
  47. }
  48. catch (ClassNotFoundException localClassNotFoundException){
  49. throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  50. }
  51. }
  52. }

事情逐渐明朗起来,从这个动态类的源码,可以分析出: $Proxy0 ,在构建这个类时,会调用了父类Proxy的构造方法,将InvocationHandler引用传递给了父类Proxy的 h 属性,于是当我们在外界使用 代理实例 调用了 callLunch() 这个方法时,就会来到这一句 this.h.invoke(this, m3, null); 由于h属性其实是InvocationHandler引用,调用了它的invoke,也就导致了上面示例代码中的RoommateInvocationHandler类的重写过的invoke方法也就被调用了,RoommateInvocationHandler也持有委托类的引用,所以委托类的方法也被调用起来了。

Java的继承机制是单继承,多接口。代理类因为必须要继承Proxy类,所以java的动态代理只能对接口进行代理,无法对一个class类进行动态代理。

动态代理原理总结

用大白话的方式讲:

有一个类InvocationHandler,它的性质类似一个中介,中介类构建时持有了委托对象,所以可以在它的invoke方法中调用了委托对象实现接口的具体方法。当外部调用这个InvocationHandler的invoke方法时,对 invoke 的调用最终都转为对委托对象的方法调用。

创建明面上负责代理的代理实例时,在内存中动态生成的类不但继承了Proxy,也实现了与委托对象相同的接口,因此代理实例可以调用此接口的方法,然后通过持有的中介类对象来调用中介类对象的invoke方法,最终达到代理实例执行了委托者的方法。

用大白话讲Java动态代理的原理的更多相关文章

  1. java高级---->Java动态代理的原理

    Java动态代理机制的出现,使得 Java 开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类.代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程 ...

  2. java动态代理的原理

    在许多mvc框架中,经常用到注解来实现面向切面(aop)的编程.面向切面编程,可以对业务逻辑中各部分进行分离,提高程序的重用性,降低各逻辑业务部分的耦合度. jdk中利用反射原理使用Proxy类对对象 ...

  3. java动态代理原理

    我们经常会用到Java的动态代理技术, 虽然会使用, 但是自己对其中的原理却不是很了解.比如代理对象是如何产生的, InvocationHandler的invoke方法是如何调用的?今天就来深究下Ja ...

  4. JAVA动态代理 你真的完全了解Java动态代理吗?

    网上讲JAVA动态代理,说的天花乱坠,发现一篇文章写的通俗易懂,特意转载过来 原文地址:https://www.jianshu.com/p/95970b089360 动态代理看起来好像是个什么高大上的 ...

  5. Java动态代理简单应用

    概念 代理模式是基本的设计模式之一,它是开发者为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象.这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色. Java动态代理比 ...

  6. 深入浅出Java动态代理

    文章首发于[博客园-陈树义],点击跳转到原文深入浅出Java动态代理 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中,更是有举足轻重的地位.代理 ...

  7. Java动态代理 深度详解

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

  8. Java动态代理:一个面包店的动态代理帝国

    文章首发于[博客园-陈树义],点击跳转到原文大白话说Java动态代理:一个面包店的动态代理帝国 代理模式是设计模式中非常重要的一种类型,而设计模式又是编程中非常重要的知识点,特别是在业务系统的重构中, ...

  9. java动态代理——代理方法的假设和验证及Proxy源码分析五

    前文地址 https://www.cnblogs.com/tera/p/13419025.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...

随机推荐

  1. apply()方法和call()介绍

    我们发现apply()和call()的真正用武之地是能够扩充函数赖以运行的作用域. 1.call,apply都属于Function.prototype的一个方法,它是JavaScript引擎内在实现的 ...

  2. Prince and princess——需要优化的DP

    一个时间效率为o(nlogn)的算法求公共子序列的应用 Prince and princess 题目大意(已翻译 ) 在nxn的棋盘上,王子和公主玩游戏.棋盘上的正方形编号为1.2.3 ... n * ...

  3. 前端性能优化_css加载会造成哪些阻塞现象?

    css的加载是不会阻塞DOM的解析,但是会阻塞DOM的渲染,会阻塞link后面js语句的执行.这是由于浏览器为了防止html页面的重复渲染而降低性能,所以浏览器只会在加载的时候去解析dom树,然后等在 ...

  4. css伪选择器使用总结——css中关于伪类和伪元素的知识总汇

    CSS 伪类用于向某些选择器添加特殊的效果,而CSS引入伪类和伪元素的概念是为了实现基于文档树之外的信息的格式化.这里讲总结关于css伪类和伪元素的相关使用 伪元素 :before/:before 在 ...

  5. 6.29模拟赛 (T1:李时珍的皮肤衣 T2:马大嘴的废话 T3:SSY的队列 T4:清理牛棚);

    啊,又是考炸的一天,成功的退步了三名,啊,成共的看错了T1  的题意 ,水了80分. 第十五名就是我,额,已经有点倒数的感觉了,并且一道题都没AC  我太难了. 好了,废话不多说了,下面正式提接: 这 ...

  6. postman-3-请求

    请求头 单击Headers选项卡将显示请求头键-值编辑器.我们可以将任何字符串设置为请求头名称.在输入字段时,自动完成下拉菜单将补充常见HTTP请求头. Content-Type标题的值也可从自动完成 ...

  7. Tomcat 架构原理解析到架构设计借鉴

    Tomcat 发展这么多年,已经比较成熟稳定.在如今『追新求快』的时代,Tomcat 作为 Java Web 开发必备的工具似乎变成了『熟悉的陌生人』,难道说如今就没有必要深入学习它了么?学习它我们又 ...

  8. day65 django进阶(1)

    目录 一.聚合查询与分组查询 1 聚合查询(aggregate) 2 分组查询(annotate) 二.F与Q查询 1 F查询的三个功能 1.1 能帮助我们直接获取到表中某个字段对应的数据 1.2 获 ...

  9. Mysql基础(十一):流程控制结构、分支结构、循环结构

    流程控制结构 说明:顺序结构:程序从上往下依次执行分支结构:程序按条件进行选择执行,从两条或多条路径中选择一条执行循环结构:程序满足一定条件下,重复执行一组语句 分支结构 特点:1.if函数功能:实现 ...

  10. cnn卷积理解

    首先输入图像是28*28处理好的图. 第一层卷积:用5*5的卷积核进行卷积,输入为1通道,输出为32通道.即第一层的输入为:28*28图,第一层有32个不同的滤波器,对同一张图进行卷积,然后输出为32 ...