码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2018-04-29 21:17:35      阅读:181      评论:0      收藏:0      [点我收藏+]

标签: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,以及其他一下需要对方法进行增强而又不想修改源码的情况下


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


推荐阅读


技术分享图片
深入理解java注解的实现原理
技术分享图片
深入理解java的反射机制

技术分享图片
250G偷懒必看资料全集



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


技术分享图片


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

标签:java   动态代理   

原文地址:http://blog.51cto.com/4247649/2109130

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!