JDK动态代理的底层实现原理

JDK动态代理的底层实现原理

     动态代理是许多框架底层实现的基础,比如Spirng的AOP等,其实弄清楚了动态代理的实现原理,它就没那么神奇了,下面就来通过案例和分析JDK底层源码来揭秘她的神秘面纱,让她一丝不挂地呈现在我们面前,邪恶了。。。

代理模式定义

存在一个代理对象,并且这个代理对象持有真实对象的引用,以实现对真实对象的访问控制。

举个例子,现在公司一般都有VPN,使我们在家也能访问到公司的内网(比如连接公司的数据库等),实现居家办公。这里VPN相当于一个代理,而公司内网相当于被代理对象,也就是真实对象。我们不能直接访问公司内网(真实对象),但是我们通过VPN(代理对象),输入身份信息,确认无误后就可以访问到公司内网。这就是代理模式的作用----控制对象的访问。

代理模式的分类

静态代理

该代理对象持有被代理对象的引用,客户端通过调用代理对象的方法间接实现调用真实对象的方法。代理对象可以在调用真实对象的方法前面加入一些操作:比如身份验证,如果身份验证没有通过,则不能访问真实对象的方法,否则可以调用真实对象的方法;也可以在调用真实对象方法后,加入一些操作,比如记录访问日志。

真实对象接口,提供两个服务方法

/**
 *
 * People 真实对象的接口包含两个方法
 *
 * @author: tanggao
 * @date::2017-9-27 下午3:57:20
 * @version 1.0
 */
public interface People {
    voidsayHello(String msg);
 
    voidsayBye(String msg);
}

真实对象接口的具体实现

/**
 *
 *Student
 * 真实对象接口的实现
 *@author: tanggao
 *@date::2017-9-27 下午3:58:05
 *@version 1.0
 */
public class Student implements People {
 
    @Override
    public void sayHello(String msg) {
      System.out.println("Hello "+msg);
    }
 
    @Override
    public void sayBye(String msg) {
        System.out.println("ByeBye "+msg);
    }
 
}

代理对象:

/**
 *
 *StaticProxy
 * 代理对象,控制对真实对象的访问控制
 *@author: tanggao
 *@date::2017-9-27 下午4:01:03
 *@version 1.0
 */
public class StaticProxy implements People {
    //真实对象,客户端不能直接访问
    private Peoplepeople;
   
    publicStaticProxy(){
        this.people=new Student();
    }
 
    @Override
    public void sayHello(String msg) {
        booleanfriendFlag=true;
        if(friendFlag){
            people.sayHello(msg);
        }
        System.out.println("记录访问日志");
      
    }
 
    @Override
    public void sayBye(String msg) {
        booleanfriendFlag=true;
        if(friendFlag){
            people.sayBye(msg);
        }
        System.out.println("记录访问日志");
    }
}

客户端调用及结果:

上面就是静态代理的一个实现,通过静态代理,实现了访问控制,但是在每个真实对象方法之前都加入了访问控制代码来验证权限。如果有很多个方法,则要在每个方法调用前都加入验证权限的代码,这样非常的不灵活且有大量的重复代码,即使把验证权限抽象出来做过方法或者类,但是还是得在每个方法前加一段调用权限验证的代码,比如,一个客户端只用其中的一个方法,但是代理中两个方法都要加入权限控制,要满足其他客户端的调用需求,上面接口中只有两个方法还好,但是如果有上百个方法那岂不是很臃肿。那么有什么办法解决了,那就是动态代理

动态代理

    动态的生成代理类,而不用像静态代理一样,在编译期间进行定义类。动态代理更加灵活,不用显示的在所有方法前面或者后面加入权限验证、记录日志等操作。

 动态代理的实现如下:

其中真实对象接口和它的实现跟静态代理是一样的。

    不同的是代理类的创建,静态代理是直接新增一个代理类,而动态代理则不是。动态代理是通过JDK的Proxy和一个调用处理器InvocationHandler来实现的,通过Proxy来生成代理类实例,而这个代理实例通过调用处理器InvocationHandler接收不同的参数灵活调用真实对象的方法。

 所以我们需要做的是创建调用处理器,该调用处理器必须实现JDK的InvocationHandler

/**
 * 
 *DynamicProxyHandler
 * 调用处理器   实现动态代理必不可少的组件
 *@author: tanggao
 *@date: :2017-9-27 下午5:08:49
 *@version 1.0
 */
public class DynamicProxyHandler implements InvocationHandler {

    private People target;
    
    public DynamicProxyHandler(){
        this.target=new Student();//实例化一个真实对象
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        boolean friendFlag=true;
        Object o=null;
        if(friendFlag){ //权限验证通过    所有方法的权限验证只需要在这里加代码就行了
           o = method.invoke(target, args); 
        }
        System.out.println("记录访问日志");
        return o;
    }
    
    /**
     * 
     * 通过Proxy动态生成一个代理实例
     *@return
     *@author:tanggao
     *@date:2017-9-27 下午4:57:07
     *return:Object
     */
    public Object getProxy(){
        /*
         * Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
         * 方法的第一个参数的作用就是 获取当前类的类加载器,作用是用来生成类的
         * 第二个参数是获取真实对象的所有接口    获取所有接口的目的是用来生成代理的,因为代理要实现所有的接口
         * 第三个参数是 调用处理器  这里传入调用处理器,是因为生成代理实例需要 调用处理器    为什么需要调用处理器,因为生成的代理不能直接调用真实对象的方法,
         * 而是通过调用处理器来调用真实对象的方法,具体就是通过上面定义的DynamicProxyHandler重写父类InvocationHandler的invoke方法
         */
       return  Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this );
    }
}

代码解释:

方法:Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)

方法的第一个参数ClassLoader是类加载器,作用是用来生成类的

 第二个参数interfaces是获取真实对象的所有接口,获取所有接口的目的是用来生成代理的,因为代理要实现所有的接口

 第三个参数InvocationHandler是调用处理器,这里传入调用处理器,是因为生成代理实例需要调用处理器,至于为什么需要调用处理器,是因为生成的代理不能直接调用真实对象的方法,而是通过调用处理器来调用真实对象的方法,具体就是上面DynamicProxyHandler重写父类的InvocationHandler的invoke方法来调用真实对象的方法。为什么是这样,后面再分析,先来看客户端怎么调用

    这样则实现了动态代理,客户端调用代理不同的方法,都实现了对真实对象的间接调用,并且经过了代理对象的权限验证。但是我们只在一个地方加入了权限验证的代码,并没有在每个方法前面都加入,这样更加灵活和优雅。但是我们重头到尾都没有看到像静态代理类那样的一个动态代理类,那么JDK的Proxy是怎么得到动态代理类的实例的呢?真的不建立一个类,就能获取该类的实例吗?

这是不可能的,Java中必须要有类,才会有该类的实例。其实不是没有代理类,而是JDK在运行期间帮我们生成了一个代理类的字节码,通过类加载器加载这个字节码,然后执行引擎进行一系列处理后生成代理类,再进行实例化。

下面就来看JDK是怎么生成代理类并且实例化的:

核心代码就是:

Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this );

看看JDK的底层实现:

贴出代码,去掉了异常和判断

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
      
     
        Class<?> cl = getProxyClass(loader, interfaces);
       
// Class[] constructorParams =
         { InvocationHandler.class };

            Constructor cons = cl.getConstructor(constructorParams);
            return cons.newInstance(new Object[] { h });
        } 

 通过源码可以知道,生成代理类是通过如下方法实现的:

Class<?> cl =getProxyClass(loader, interfaces);

再追踪一下,这个方法里面内容很多,但是最关键的就是下面这个方法:

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(

                    proxyName, interfaces);

通过这个方法就生成了代理类的字节码,只不过调用完就不存在了。所以我们看不到它的源码。

我们也可以模拟生成代理类,看看生成的动态代理类到底是个什么东西

 通过如下类就可以生成代理类的字节码

/**
 * 
 *GenerateDynamic
 * 生成代理类字节码的类
 *@author: tanggao
 *@date: :2017-9-27 下午5:36:54
 *@version 1.0
 */
public class GenerateDynamic {

    public static void main(String[] args) {
       /* People p = new Student();

        DynamicProxyHandler handler = new DynamicProxyHandler();

        People proxy = (People) Proxy.newProxyInstance(handler.getClass()
                .getClassLoader(), p.getClass().getInterfaces(), handler);

        proxy.sayBye("tanggao");*/

        createProxyClassFile();
    }

    private static void createProxyClassFile() {
        String name = "ProxySubject";
        byte[] data = ProxyGenerator.generateProxyClass(name,
                new Class[] { People.class });
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(name + ".class");
            System.out.println((new File("hello")).getAbsolutePath());
            out.write(data);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        finally {
            if (null != out)
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
}

用反编译工具查看一下生成的字节码,先上代码,后面再分析:

import com.dynamic.test.People;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxySubject extends Proxy
  implements People
{
  private static Method m1;
  private static Method m3;
  private static Method m4;
  private static Method m0;
  private static Method m2;

  public ProxySubject(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void sayBye(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final void sayHello(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m4, new Object[] { paramString });
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    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.dynamic.test.People").getMethod("sayBye", new Class[] { Class.forName("java.lang.String") });
      m4 = Class.forName("com.dynamic.test.People").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

分析过程直接看图:

所以可以看到生成的动态代理类其实跟静态代理类还是有区别的,静态代理是我们直接控制真实对象的方法调用,而动态代理是通过调用处理器的invoke方法来调用真实对象的方法,而这个invoke方法就是我们自己覆写的方法

可以看出是通过反射实现的,通过传入的不同的方法对象和参数来调用真实对象的不同方法。刚开始我和网上很多人一样都有一个疑问,对于invoke方法发参数,Method和args在我们覆写的invoke方法中都有用到,但是对于第一个参数,代理对象proxy没有用,所以不知道这个东西调用处理器传给我们有什么用,最后通过谷歌和百度搜索发现,网上也有人和我有一样的疑问

这是原文的链接:http://stackoverflow.com/questions/22930195/understanding-proxy-arguments-of-the-invoke-method-of-java-lang-reflect-invoca

原文对这个参数的解释是:

1.可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。

2.可以将代理对象返回以进行连续调用,这就是proxy存在的目的。因为this并不是代理对象,

下面是给出的例子

public interface Account {
    public Account deposit (double value);
    public double getBalance ();
}

实现类

public class ExampleInvocationHandler implements InvocationHandler {
 
    private double balance;
 
    @Override
    public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
 
        // simplified method checks, would need to check the parameter count and types too
        if ("deposit".equals(method.getName())) {
            Double value = (Double) args[0];
            System.out.println("deposit: " + value);
            balance += value;
            return proxy; // here we use the proxy to return 'this'
        }
        if ("getBalance".equals(method.getName())) {
            return balance;
        }
        return null;
    }
 
}

测试调用

public class Test {
 
    /**
     * description:
     *
     * @param args
     * @author:tanggao
     * @date:2017-9-28 下午2:42:00 return:void
     */
    public static void main(String[] args) {
        Account account = (Account) Proxy.newProxyInstance(Account.class
                .getClassLoader(), new Class[] { Account.class,
                Serializable.class }, new ExampleInvocationHandler());
 
        // method chaining for the win!
        account.deposit(5000).deposit(4000).deposit(-2500);
        System.out.println("Balance: " + account.getBalance());
 
    }
 
}

我们看到如果返回proxy的话可以对该代理对象进行连续调用

那为什么不返回this,而是返回proxy对象呢?

因为this对象的类型是ExampleInvocationHandler,而不是代理类$Proxy0

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏转载gongluck的CSDN博客

c++ 中__declspec 的用法

c++ 中__declspec 的用法 语法说明: __declspec ( extended-decl-modifier-seq ) 扩展修饰符: ...

6847
来自专栏达摩兵的技术空间

js中的作用域

相信自从es6出来之后,你一定多少知道或者已经在项目中实践了部分的块级作用域,在函数或者类的内部命名变量已经在使用let了,但是你知道它真正的作用是什么吗?又是...

1062
来自专栏V站

PHP反序列化深入理解

在PHP中右serialize()和unserialize()两个函数,php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。u...

1765
来自专栏云霄雨霁

Java--类和对象之基础知识

1503
来自专栏大闲人柴毛毛

深入Java虚拟机——JVM内存详解

在C++中,程序员拥有每一个对象的所有权,但与此同时还肩负着释放对象内存空间的责任;而Java由于有了虚拟机的帮助,程序员拥有对象的所有权的同时不再需要释放对象...

35613
来自专栏吴伟祥

Shell脚本学习总结(二) 流程控制 转

Shell case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。case语句格式如下:

712
来自专栏张俊红

零基础学习爬虫并实战

总第63篇 本篇主要从爬虫是什么、爬虫的一般流程、爬虫各个流程的实现方法、爬虫实例四个方面分享零基础了解爬虫,并进行简单的实战。 在阅读下面之前,我们...

2.5K10
来自专栏有趣的Python

慕课网-Linux C语言指针与内存-学习笔记

Linux C语言指针与内存 工具与原理 指针 数组 字符串 堆内存与栈内存 gdb内存调试工具。 C语言中指针的基本用法 #include <stdio.h>...

2834
来自专栏mukekeheart的iOS之旅

《JavaScript高级程序设计》学习笔记(3)——变量、作用域和内存问题

欢迎关注本人的微信公众号“前端小填填”,专注前端技术的基础和项目开发的学习。 本节内容对应《JavaScript高级程序设计》的第四章内容。 1、函数:通过函数...

2906
来自专栏破晓之歌

python 模板实现-引擎的编写(有时间试一下)

1.模板的编写:https://blog.csdn.net/MageeLen/article/details/68920913

1273

扫码关注云+社区