java动态代理原理

一、代理的概念

  动态代理技术是整个java技术中最重要的一个技术它是学习java框架的基础,不会动态代理技术,那么在学习Spring这些框架时是学不明白的。

  动态代理技术就是用来产生一个对象的代理对象的在开发中为什么需要为一个对象产生代理对象呢?
  举一个现实生活中的例子:歌星或者明星都有一个自己的经纪人,这个经纪人就是他们的代理人,当我们需要找明星表演时,不能直接找到该明星,只能是找明星的代理人。比如刘德华在现实生活中非常有名,会唱歌,会跳舞,会拍戏,刘德华在没有出名之前,我们可以直接找他唱歌,跳舞,拍戏,刘德华出名之后,他干的第一件事就是找一个经纪人,这个经纪人就是刘德华的代理人(代理),当我们需要找刘德华表演时,不能直接找到刘德华了(刘德华说,你找我代理人商谈具体事宜吧!),只能是找刘德华的代理人,因此刘德华这个代理人存在的价值就是拦截我们对刘德华的直接访问!
  这个现实中的例子和我们在开发中是一样的,我们在开发中之所以要产生一个对象的代理对象,主要用于拦截对真实业务对象的访问。那么代理对象应该具有什么方法呢?代理对象应该具有和目标对象相同的方法。

  所以在这里明确代理对象的两个概念:
    1、代理对象存在的价值主要用于拦截对真实业务对象的访问
    2、代理对象应该具有和目标对象(真实业务对象)相同的方法

    刘德华(真实业务对象)会唱歌,会跳舞,会拍戏,我们现在不能直接找他唱歌,跳舞,拍戏了,只能找他的代理人(代理对象)唱歌,跳舞,拍戏,一个人要想成为刘德华的代理人,那么他必须具有和刘德华一样的行为(会唱歌,会跳舞,会拍戏),刘德华有什么方法,他(代理人)就要有什么方法,我们找刘德华的代理人唱歌,跳舞,拍戏,但是代理人不是真的懂得唱歌,跳舞,拍戏的,真正懂得唱歌,跳舞,拍戏的是刘德华,在现实中的例子就是我们要找刘德华唱歌,跳舞,拍戏,那么只能先找他的经纪人,交钱给他的经纪人,然后经纪人再让刘德华去唱歌,跳舞,拍戏。

二、java中的代理

         使用代理模式必须要让代理类和目标类实现相同的接口,客户端通过代理类来调用目标方法,代理类会将所有的方法调用,分派到目标对象上反射执行,还可以在分派过程中添加"前置通知"和后置处理(是在invoke方法中添加,如在调用目标方法前校验权限,在调用完目标方法后打印日志等)等功能。

 

使用动态代理的五大步骤

1.通过实现InvocationHandler接口来自定义自己的InvocationHandler(主要是编写invoke方法);

2.通过Proxy.getProxyClass获得动态代理类

3.通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class)

4.通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入

5.通过代理对象调用目标方法

2.1、"java.lang.reflect.Proxy"类介绍

  现在要生成某一个对象的代理对象,这个代理对象通常也要编写一个类来生成,所以首先要编写用于生成代理对象的类。在java中如何用程序去生成一个对象的代理对象呢,java在JDK1.5之后提供了一个"java.lang.reflect.Proxy"类,通过"Proxy"类提供的一个newProxyInstance方法用来创建一个对象的代理对象,如下所示:

     static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 

  newProxyInstance方法用来返回一个代理对象,这个方法总共有3个参数:

          ClassLoader loader:用来指明生成代理对象使用哪个类装载器

          Class<?>[] interfaces:用来指明生成哪个对象的代理对象(代理类和目标类实现相同的接口

        InvocationHandler h : 用来指明产生的这个代理对象要做什么事情。(这个InvocationHandler 就是我们自己定义的InvocationHandler 对象,里边有我们编写invok方法

newProxyInstance方法生成代理对象过程:

    a. 通过Proxy.getProxyClass(ProxyGenerator.generateProxyClass(proxyName, interfaces);)获得动态代理类的class字节码内容。
    b. 把字节码通过传入的类加载器加载到虚拟机中,然后通过反射机制获得代理类的构造方法(方法签名为getConstructor(InvocationHandler.class)),生成代理类对象

      那为什么通过调用newProxyInstance方法得到的代理对象,即可以实现拦截添加前置、后置处理,有可以调用目标类的方法呢?

    输出通过调用newProxyInstance方法得到的代理对象的字节码,存储成class文件,在反编译成Java 代码,我们分析下生成的这个代理对象的Proxy0.java代码:

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

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

///////

static{
    m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("sing", new Class[] { Class.forName("java.lang.String") });
    m4 = Class.forName("com.sun.proxy.$Proxy0").getMethod("dance", new Class[] { Class.forName("java.lang.String") });
      
    m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
    m6 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getInvocationHandler", new Class[] { Class.forName("java.lang.Object") });
    m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
    m7 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getProxyClass", new Class[] { Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;") });
    
    m12 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getClass", new Class[0]);
    m14 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notifyAll", new Class[0]);
    m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
    m9 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[0]);
    m13 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notify", new Class[0]);
    m8 = Class.forName("com.sun.proxy.$Proxy0").getMethod("newProxyInstance", new Class[] { Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"), Class.forName("java.lang.reflect.InvocationHandler") });
    m11 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[] { Long.TYPE });
    m5 = Class.forName("com.sun.proxy.$Proxy0").getMethod("isProxyClass", new Class[] { Class.forName("java.lang.Class") });
    m10 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[] { Long.TYPE, Integer.TYPE });
}

dance(String paramString) 与之相呼应:代理类和目标类实现相同的接口

h.invoke(this, m4, new Object[] { paramString });    //m4:代理类的方法,和目标类的方法,一一对应。

 m4 = Class.forName("com.sun.proxy.$Proxy0").getMethod("dance", new Class[] { Class.forName("java.lang.String") });

与之相呼应: 这里调用的是我们自定义的InvocationHandler 对象h的invoke方法,所以就实现了可以拦截、添加前置后置处理。

       

       这也说明了代理逻辑 和 动态代理本身是代码分离的,程序员只需要关注好自己的代理逻辑就行,动态代理本身就交给jdk本身去处理。在jdk动态代理中,美中不足就是整个设计都是针对接口做的代理,如果是普通的类,我们无法通过这个方式代理对象(通过生成的代理类也知道没有接口是不行的),但是我们知道 通过拼接字节码生成新的类自由度是十分大的,这也就启示我们 设计不管是针对接口类还是普通类的代理类 是完全可行的,比如cglib框架就是通过拼接字节码来实现非接口类的代理 后面会介绍如何 实现这种操作,我在自己的Simplify-Core项目中已经尝试通过asm框架(字节码读写框架)实现代理操作。
 

参考文章:http://www.cnblogs.com/MOBIN/p/5597215.html

                  http://www.importnew.com/23168.html

猜你喜欢

转载自blog.csdn.net/lusa1314/article/details/83347945
今日推荐