`

深入理解java动态代理的实现机制

    博客分类:
  • java
阅读更多

今天将从以下5方面来系统的学习一下java动态代理的实现机制:

  • 什么是代理

  • 什么是静态代理

  • 什么是动态代理

  • 动态代理的实现机制

  • 动态代理的使用场景

     

1,什么是代理

 

相信大家都有购买过火车票或者机票的经历,有的人在携程买,有的在飞猪,也有的在微信上买等等,这里的携程飞猪微信也好都是受铁路部的委托代理售卖火车票,这里的携程飞猪就是代理类,铁路部就是委托类,这就是代理

 

2,什么是静态代理

 

所谓的静态代理就是在代码运行之前,代理类就已经存在,通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类,之前文章一分钟了解设计模式中的代理模式就是静态代理,具体可以点进去查阅

 

3,什么是动态代理

 

代理类在程序运行时创建的代理方式被成为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指令”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数,这么说比较抽象,下面我们结合一个实例来介绍一下动态代理的这个优势是怎么体现的

现在,假设我们要实现这样一个需求:我们还是以在飞猪上购买车票为例,飞猪在执行购买火车票之前需要先检查用户认证,在购买完毕后需要保存数据到自己对应的平台。首先我们来使用静态代理来实现这一需求,相关代码如下:

 

/**
* 定义一个代理购买火车票的类
*
* @author zhangqh
* @date 2018年4月29日
*/
public class ProxyChepiao implements Huochepiao {
   /**
    * 真正有权利生成火车票的地方
    */
   private Tieluju tieluju;
   @Override
   public void buyHuochepiao(String name) {
       if(tieluju == null){
           tieluju = new Tieluju();
       }
       System.out.println("买票前验证用户真实性................");
       tieluju.buyHuochepiao(name);
       System.out.println("买票后成功后数据录入自己平台.............");
   }
}

 

从以上代码中我们可以了解到,通过静态代理实现我们的需求需要我们在每个方法中都添加相应的逻辑,这里只存在两个方法所以工作量还不算大,假如接口中包含上百个方法呢?这时候使用静态代理就会编写许多冗余代码。通过使用动态代理,我们可以对所有代理类的方法进行统一处理,而不用逐一修改每个方法。下面我们来具体介绍下如何使用动态代理方式实现我们的这个需求如下

 

定义一个代理类与委托类之间的中介类:

 

 

/**
* 动态代理类
*
* @author zhangqh
* @date 2018年4月29日
*/
public class DynamicProxy implements InvocationHandler {
   /**
    * 要代理的真实对象
    */
   private Object obj;
   /**
    * 构造方式 初始化要代理的真实对象
    * @param obj
    */
   public DynamicProxy(Object obj) {
       this.obj = obj;
   }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args)
           throws Throwable {
       System.out.println("买票前验证用户真实性................");
       method.invoke(obj, args);
       System.out.println("买票后成功后数据录入自己平台.............");
       return null;
   }
}

 

动态生成代理类如下:

 

// 要代理的真实对象 铁路部
       Huochepiao tielubu = new Tieluju();
       // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
       InvocationHandler handler = new DynamicProxy(tielubu);
       /*
        * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
        * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
        * 第二个参数tielubu.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
        * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
        */
       Huochepiao subject = (Huochepiao)Proxy.newProxyInstance(handler.getClass().getClassLoader(), tielubu
               .getClass().getInterfaces(), handler);
       subject.buyHuochepiao("小芳");
       subject.buyHuochepiao("小明");

 

运行结果如下:

 

买票前验证用户真实性................
铁路部门为【小芳】生成一张火车票了
买票后成功后数据录入自己平台.............
买票前验证用户真实性................
铁路部门为【小明】生成一张火车票了
买票后成功后数据录入自己平台.............

 

4,动态代理的实现机制

 

以上实现了java动态代理的完整例子,下边来看看他的具体实现机制主要两个类:

 

  • InvocationHandler

  • Proxy

 

首先看看InvocationHandler接口,它就只有一个方法invoke如下:

 

public Object invoke(Object proxy, Method method, Object[] args)
       throws Throwable;

 

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke方法来进行调用,我们看到invoke方法一共接受三个参数,那么这三个参数分别代表什么呢?

 

proxy - 在其上调用方法的代理实例也就是代理的真实对象
method - 指的是我们所要调用真实对象的某个方法的Method对象
args - 指的是调用真实对象某个方法时接受的参数

接下来我们来看看Proxy这个类,这个类的方法就比较多了,我们这边主要看newProxyInstance这个方法如下:

 

 

@CallerSensitive
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);
   }
   // 生成代理类
   Class<?> cl = getProxyClass0(loader, intfs);
   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;
               }
           });
       }
       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);
   }
}

 

进去getProxyClass0可以看到代理类从proxyClassCache缓存中获取,代码如下:

 

private static Class<?> getProxyClass0(ClassLoader loader,
                                          Class<?>... interfaces) {
       if (interfaces.length > 65535) {
           throw new IllegalArgumentException("interface limit exceeded");
       }
       // If the proxy class defined by the given loader implementing
       // the given interfaces exists, this will simply return the cached copy;
       // otherwise, it will create the proxy class via the ProxyClassFactory
       return proxyClassCache.get(loader, interfaces);
   }

 

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
       proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

 

具体缓存是怎么生成的可以再ProxyClassFactory()进去看如下:其中最重要的就是ProxyGenerator.generateProxyClass这个生成代理类字节码

 

private static final class ProxyClassFactory implements
       BiFunction<ClassLoader, Class<?>[], Class<?>> {
   // prefix for all proxy class names
   private static final String proxyClassNamePrefix = "$Proxy";
   // next number to use for generation of unique proxy class names
   private static final AtomicLong nextUniqueNumber = new AtomicLong();
   @Override
   public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
       Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(
               interfaces.length);
       String proxyPkg = null; // package to define proxy class in
       int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
       // 中间省略了一些无关紧要的代码 .......
       // 循环遍历目标类所实现的接口
       for (Class<?> intf : interfaces) {
           int flags = intf.getModifiers();
           if (!Modifier.isPublic(flags)) {
               accessFlags = Modifier.FINAL;
               String name = intf.getName();
               int n = name.lastIndexOf('.');
               String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
               if (proxyPkg == null) {
                   proxyPkg = pkg;
               } else if (!pkg.equals(proxyPkg)) {
                   throw new IllegalArgumentException(
                           "non-public interfaces from different packages");
               }
           }
       }
       long num = nextUniqueNumber.getAndIncrement();
       String proxyName = proxyPkg + proxyClassNamePrefix + num;
       // 生成代理类的字节码
       byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
               interfaces, accessFlags);
       try {
           // 根据代理类的字节码生成代理类的实例
           return defineClass0(loader, proxyName, proxyClassFile, 0,
                   proxyClassFile.length);
       } catch (ClassFormatError e) {
           throw new IllegalArgumentException(e.toString());
       }
   }
}

 

接下来我们再来看另外一个问题我们的动态代理中间处理类DynamicProxy中的invoke是由谁来调用的?以上我介绍过代理类的jdk底层的具体实现方法,那么现在我们通过ProxyGenerator.generateProxyClass来演示手动生成一个代码类,代码如下:

 

/**
* 手动生成动态代理的字节码文件
*
* @author zhangqh
* @date 2018年4月29日
*/
public class ProxyGeneratorUtils {
   /**
    * 把代理类的字节码写到硬盘上
    * @param path 保存路径
    */  
   public static void writeProxyClassToHardDisk(String path) {  
       // 获取代理类的字节码  
       byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy666",Tieluju.class.getInterfaces());  
       FileOutputStream out = null;  
       try {  
           out = new FileOutputStream(path);  
           out.write(classFile);  
           out.flush();  
       } catch (Exception e) {  
           e.printStackTrace();  
       } finally {  
           try {  
               out.close();  
           } catch (IOException e) {  
               e.printStackTrace();  
           }  
       }  
   }
   public static void main(String[] args) {
       writeProxyClassToHardDisk("e:/$Proxy666.class");
   }
}

 

我们的本地电脑E盘中生成一个$Proxy666.class文件,反编译如下:

 

 

import com.zhang.proxy.Huochepiao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy666 extends Proxy implements Huochepiao {
   private static Method m1;
   private static Method m3;
   private static Method m2;
   private static Method m0;
   public $Proxy666(InvocationHandler paramInvocationHandler)
   {
       super(paramInvocationHandler);
   }
   public final boolean equals(Object paramObject)
   {
       try {
           return ((Boolean) this.h.invoke(this, m1,
                   new Object[] { paramObject })).booleanValue();
       } catch (RuntimeException localRuntimeException) {
           throw localRuntimeException;
       } catch (Throwable localThrowable) {
           throw new UndeclaredThrowableException(localThrowable);
       }
   }
   public final void buyHuochepiao(String paramString)
   {
       try {
           this.h.invoke(this, m3, new Object[] { paramString });
           return;
       } catch (RuntimeException localRuntimeException) {
           throw localRuntimeException;
       } catch (Throwable localThrowable) {
           throw new UndeclaredThrowableException(localThrowable);
       }
   }
   public final String toString()
   {
       try {
           return (String) this.h.invoke(this, m2, null);
       } catch (RuntimeException localRuntimeException) {
           throw localRuntimeException;
       } catch (Throwable localThrowable) {
           throw new UndeclaredThrowableException(localThrowable);
       }
   }
   public final int hashCode()
   {
       try {
           return ((Integer) this.h.invoke(this, m0, null)).intValue();
       } catch (RuntimeException localRuntimeException) {
           throw localRuntimeException;
       } catch (Throwable localThrowable) {
           throw new UndeclaredThrowableException(localThrowable);
       }
   }
   static {
       try {
           m1 = Class.forName("java.lang.Object").getMethod("equals",
                   new Class[] { Class.forName("java.lang.Object") });
           m3 = Class.forName("com.zhang.proxy.Huochepiao").getMethod(
                   "buyHuochepiao",
                   new Class[] { Class.forName("java.lang.String") });
           m2 = Class.forName("java.lang.Object").getMethod("toString",
                   new Class[0]);
           m0 = Class.forName("java.lang.Object").getMethod("hashCode",
                   new Class[0]);
           return;
       } catch (NoSuchMethodException localNoSuchMethodException) {
           throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
       } catch (ClassNotFoundException localClassNotFoundException) {
           throw new NoClassDefFoundError(
                   localClassNotFoundException.getMessage());
       }
   }
}

 

好了,到目前为止,谁调用的invoke已经很明显了吧,整个java的动态代理的具体机制就都讲完了,现在再用JDK动态代理的时候就不只会用而已了,就能达到了“知其然,知其所以然”了吧  

注意:jdk动态代理只能代理接口,之所以只能代理接口是因为代理类本身已经extends了Proxy,而根据java特性是不允许多重继承,但是允许实现多个接口,而cglib是支持动态的生成基于实现的代理类的,具体怎么生成的这边就不详述了,感兴趣的可以自己到网上查阅

5,动态代理的使用场景

 

上边详细的讲述了一下java动态代码的实现机制,那它到底可以用在哪些使用场景呢,如下:

a,Spring的AOP机制就是采用动态代理的机制来实现切面编程

b,我们在使用RPC框架的时候,框架本身并不能提前知道各个业务方要调用哪些接口的哪些方法 ,那么这个时候,就可用通过动态代理的方式来建立一个中间人给客户端使用

c,以及其他一下需要对方法进行增强而又不想修改源码的情况下

 

以上是今天文章的所有内容,欢迎大家吐槽酷 


 

 

更多优质文章请关注以下公众号查阅:

 

分享到:
评论

相关推荐

    SpringAOP的实现机制(底层原理)、应用场景等详解,模拟过程的实例

    您将了解如何使用Java的反射机制来创建代理对象,以及如何将横切逻辑注入到目标方法中。我们还提供了实际示例,演示如何在Spring AOP中使用JDK动态代理。 CGLib动态代理: 我们将深入研究CGLib动态代理,它允许您在...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    正统的类加载器架构 / 232 9.2.2 OSGi:灵活的类加载器架构 / 235 9.2.3 字节码生成技术与动态代理的实现 / 238 9.2.4 Retrotranslator:跨越JDK版本 / 242 9.3 实战:自己动手实现远程执行功能 / 246 9.3.1 ...

    深入理解Spring声明式事务:源码分析与应用实践

    这种代理机制基于Spring AOP实现,能够在运行时动态地管理事务的创建、提交或回滚。此外,Spring事务管理器支持多种类型的事务策略,包括不同的传播行为和隔离级别,允许开发者根据具体业务场景选择最合适的事务管理...

    深入解析Spring AOP源码:从原理到实现,全方位掌握Java AOP编程精髓

    Spring AOP(面向切面编程)作为Spring框架的一个重要部分,为Java开发者提供了一个强大而灵活的工具来切入代码执行流程,实现关注点的分离。通过详细解析Spring AOP的源码,本文揭示了其背后的核心原理和实现机制。...

    Java思维导图xmind文件+导出图片

    理解通信协议传输过程中的序列化和反序列化机制 基于框架的RPC通信技术 WebService/ApacheCXF RMI/Spring RMI Hession 传统RPC技术在大型分布式架构下面临的问题 分布式架构下的RPC解决方案 Zookeeper ...

    Java反射机制 深入浅出

    1、到底什么叫反射 2、Class类的作用 3、Class类的实例化三种...Class类在一般的基本开发是不会有任何作用的,完全可以不会,但是,对于一些高端的开发框架,所有的基本的核心原理都在于反射机制的应用上。 8、代理模式

    Spring AOP源码深度解析:掌握Java高级编程核心技术

    本文深入分析了Spring AOP的实现机制,让读者能够更好地理解和应用这一强大的编程范式。 Spring AOP是基于代理模式实现的,主要包括动态代理、通知(Advice)、切点(Pointcut)、切面(Aspect)和连接点(Join ...

    java8集合源码分析-java-agent:基于java5Instrumentapi实现的mock框架

    java8 集合源码分析 1 介绍 1.1 用途 单元测试mock 联调、集成测试mock 支持mock静态方法,final方法,私有...理解java类加载机制、tomcat类加载机制 学习groovy语言 学习使用javassist增强字节码 了解dubbo消费端执

    疯狂JAVA讲义

    1.3 Java程序运行机制 5 1.3.1 高级语言的运行机制 6 1.3.2 Java程序的运行机制和JVM 6 1.4 开发Java的准备 7 1.4.1 安装JDK 8 学生提问:不是说JVM是运行Java程序的虚拟机吗?那JRE和JVM的关系是怎样的呢? 8 ...

    Java开发详解.zip

    031505_【第15章:Java反射机制】_动态代理笔记.pdf 031506_【第15章:Java反射机制】_工厂设计模式笔记.pdf 031601_【第16章:Annotation】_系统内建Annotation笔记.pdf 031602_【第16章:Annotation】_自定义...

    Java虚拟机

    9.2.3 字节码生成技术与动态代理的实现 9.2.4 Retrotranslator:跨越JDK版本 9.3 实战:自己动手实现远程执行功能 9.3.1 目标 9.3.2 思路 9.3.3 实现 9.3.4 验证 9.4 本章小结 第四部分 程序编译与代码优化...

    安卓java读取网页源码-AndroidLearningNotes:第一次提交

    安卓java读取网页源码 AndroidLearningNotes Java Java基础面试知识 int与integer的区别 探探对java多态的理解 String、StringBuffer、StringBuilder区别 ...静态代理和动态代理的区别,什么场景使用

    打造专业开发者指南:针对ShardingProxy分库分表解决策略的深度剖析 – 详解部署、使用、服务治理与优化技巧

    本文首先深入讲解了ShardingProxy的部署和使用,比如映射设置,数据库代理服务部署,以及如何连接到它。 然后提到了服务治理,包括数据接入、弹性伸缩、数据迁移等为主的组件。“服务治理”的这个概念,首次在分布式...

    跨平台移动端开发NativeScript.zip

    为了避免开发者需要对三个支持的平台有深入的理解,该框架包含了一个抽象与原生代码连接的NativeScript模块层(NativeScript Modules Layer,NML), 可以自动将JavaScript调用转化为原生调用。NML包含如下模 块...

    二十三种设计模式【PDF版】

    以 Jive 为例,剖析代理模式在用户级别授权机制上的应用 设计模式之 Facade(门面?) 可扩展的使用 JDBC针对不同的数据库编程,Facade提供了一种灵活的实现. 设计模式之 Composite(组合) 就是将类用树形结构组合成...

    JINI 核心技术

    第8章 深入理解:使用查找服务 154 8.1 查找概述 154 8.1.1 查找服务是Jini服务 154 8.1.2 服务如何使用查找 155 8.1.3 客户如何使用查找 155 8.2 发布服务代理:加入协议 156 8.2.1 JoinManager类 157 8.2.2 管理...

    JINI核心技术

    第8章 深入理解:使用查找服务 154 8.1 查找概述 154 8.1.1 查找服务是Jini服务 154 8.1.2 服务如何使用查找 155 8.1.3 客户如何使用查找 155 8.2 发布服务代理:加入协议 156 8.2.1 JoinManager类 157 8.2.2 管理...

    asp.net知识库

    .NET的插件机制的简单实现 我对J2EE和.NET的一点理解 难分难舍的DSO(一) InternalsVisibleToAttribute,友元程序集访问属性 Essential .NET 读书笔记 [第一部分] NET FrameWork的Collections支持 .NET的反射在软件...

    亿级流量电商详情页系统实战-缓存架构+高可用服务架构+微服务架构

    11、大公司的OneService一站式入口服务:基于商品详情页依赖数十个服务的业务特点,深入讲解了如何设计与开发大公司中常见的一站式入口服务,代理后端数十个服务,作为统一入口,打造服务闭环。 12、大型电商网站的...

Global site tag (gtag.js) - Google Analytics