In-depth understanding of the implementation mechanism of java dynamic proxy

Today, I will systematically learn the implementation mechanism of java dynamic proxy from the following five aspects:

  • what is a proxy

  • What is a static proxy

  • What is a dynamic proxy

  • Implementation Mechanism of Dynamic Agent

  • Dynamic proxy usage scenarios

     

1. What is a proxy

 

I believe that everyone has the experience of buying train tickets or air tickets. Some people buy them on Ctrip, some on Fliggy, and some on WeChat, etc. The Ctrip and Fliggy WeChat here are all entrusted by the Ministry of Railways. The agent sells train tickets. The Ctrip Fliggy here is the agent category, and the Ministry of Railways is the entrusted category. This is the agent .

 

2. What is a static proxy

 

The so-called static proxy means that the proxy class already exists before the code runs . Usually, the proxy class and the delegate class in the static proxy will implement the same interface or be derived from the same parent class . The proxy mode is static proxy, you can click to check it

 

3. What is dynamic proxy

 

The proxy method created by the proxy class when the program is running is called a dynamic proxy , that is, in this case, the proxy class is not defined in the Java code, but at runtime according to our "instructions" in the Java code. dynamically generated. Compared with static proxy, the advantage of dynamic proxy is that it can conveniently process the functions of proxy classes in a unified manner without modifying the functions of each proxy class. This is more abstract. Let's introduce dynamic proxy with an example. How is this advantage manifested?

Now, suppose we want to implement such a requirement: let's take the example of buying tickets on Fliggy. Fliggy needs to check the user authentication before purchasing train tickets, and needs to save the data to its corresponding platform after purchasing. First, let's use a static proxy to achieve this requirement. The relevant code is as follows:

 

/**
* 定义一个代理购买火车票的类
*
* @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,以及其他一下需要对方法进行增强而又不想修改源码的情况下

 

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


 

 

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

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326109067&siteId=291194637