JDK的动态代理深入解析(Proxy,InvocationHandler)(转)

 

代理模式--结构型 

JDK的动态代理深入解析(Proxy,InvocationHandler)(转)

Spring AOP 实现原理

一、什么是动态代理

  动态代理可以提供对另一个对象的访问,同时隐藏实际对象的具体事实。代理一般会实现它所表示的实际对象的接口。代理可以访问实际对象,但是延迟实现实际对象的部分功能,实际对象实现系统的实际功能,代理对象对客户隐藏了实际对象。客户不知道它是与代理打交道还是与实际对象打交道。

 

目的:主要用来做方法的增强,让你可以在不修改源码(不用改变这个方法的签名,原来调用这个方法的类依然能正常工作)的情况下,增强一些方法。在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法),因为在InvocationHandler的invoke方法中,你可以直接获取正在调用方法对应的Method对象,具体应用的话,比如可以添加调用日志,做事务控制等。

还有一个有趣的作用是可以用作远程调用,比如现在有Java接口,这个接口的实现部署在其它服务器上,在编写客户端代码的时候,没办法直接调用接口方法,因为接口是不能直接生成对象的,这个时候就可以考虑代理模式(动态代理)了,通过Proxy.newProxyInstance代理一个该接口对应的InvocationHandler对象,然后在InvocationHandler的invoke方法内封装通讯细节就可以了。具体的应用,最经典的当然是Java标准库的RMI,其它比如hessian,各种webservice框架中的远程调用,大致都是这么实现的。
而像 AspectJ 这种 AOP 刚不同,它直接把人家的 class 代码修改了,它就不需要使用代理。
这些在新的 JDK 6 中都可以通过 Instrument 来做到,不过也是个通用的方法,还得通过规则来定制什么情况下处理,什么时候不处理。

 

二、jdk的动态代理

  目前Java开发包中包含了对动态代理的支持,但是其实现只支持对接口的的实现。 其实现主要通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。 

  Proxy类主要用来获取动态代理对象,InvocationHandler接口用来约束调用者实现。

  动态代理是很多框架和技术的基础, spring 的AOP实现就是基于动态代理实现的。了解动态代理的机制对于理解AOP的底层实现是很有帮助的。

2.1、Proxy类

Porxy类也是在java.lang.reflect,Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。 

代理类具用以下属性:

  • 代理类是公共的、最终的,而不是抽象的。
  • 未指定代理类的非限定名称。但是,以字符串 "$Proxy" 开头的类名空间应该为代理类保留。
  • 代理类扩展 java.lang.reflect.Proxy
  • 代理类会按同一顺序准确地实现其创建时指定的接口。
  • 如果代理类实现了非公共接口,那么它将在与该接口相同的包中定义。否则,代理类的包也是未指定的。注意,包密封将不阻止代理类在运行时在特定包中的成功定义,也不会阻止相同类加载器和带有特定签名的包所定义的类。
  • 由于代理类将实现所有在其创建时指定的接口,所以对其 Class 对象调用 getInterfaces 将返回一个包含相同接口列表的数组(按其创建时指定的顺序),对其 Class 对象调用 getMethods 将返回一个包括这些接口中所有方法的 Method 对象的数组,并且调用 getMethod 将会在代理接口中找到期望的一些方法。
  • 如果 Proxy.isProxyClass 方法传递代理类(由 Proxy.getProxyClass 返回的类,或由 Proxy.newProxyInstance 返回的对象的类),则该方法返回 true,否则返回 false。
  • 代理类的 java.security.ProtectionDomain 与由引导类加载器(如 java.lang.Object)加载的系统类相同,原因是代理类的代码由受信任的系统代码生成。此保护域通常被授予 java.security.AllPermission
  • 每个代理类都有一个可以带一个参数(接口 InvocationHandler 的实现)的公共构造方法,用于设置代理实例的调用处理程序。并非必须使用反射 API 才能访问公共构造方法,通过调用 Proxy.newInstance 方法(将调用 Proxy.getProxyClass 的操作和调用带有调用处理程序的构造方法结合在一起)也可以创建代理实例。 

方法

protected  Proxy(InvocationHandler h)  //使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy 实例。 

static InvocationHandler getInvocationHandler(Object proxy) //返回指定代理实例的调用处理程序。

static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) //返回代理类的 java.lang.Class 对象,并向其提供类加载器和接口数组。

static boolean isProxyClass(Class<?> cl) //当且仅当指定的类通过 getProxyClass 方法或 newProxyInstance 方法动态生成为代理类时,返回 true。

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) //返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

2.1、InvocationHandler接口

InvocationHandler接口也是在java.lang.reflect,唯一的一个方法是invoke如下:

public interface InvocationHandler {

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

 这个方法有三个参数,其中第二和第三个参数都比较好理解,一个是被拦截的方法,一个是该方法的参数列表。关键是第一个参数。按照doc文档的解析,proxy - the proxy instance that the method was invoked on也就是说,proxy应该是一个代理实例(动态代理类)。

三、示例

好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:

首先我们定义了一个Subject类型的接口,为其声明了两个方法:

package com.dxz.proxy1;
public interface Subject
{
    public void rent();
    
    public void hello(String str);
}

接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject类:

package com.dxz.proxy1;

public class RealSubject implements Subject {
    @Override
    public void rent() {
        System.out.println("I want to rent my house");
    }

    @Override
    public void hello(String str) {
        System.out.println("hello: " + str);
    }
}

下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

package com.dxz.proxy1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxy implements InvocationHandler {
    // 这个就是我们要代理的真实对象
    private Object subject;

    // 构造方法,给我们要代理的真实对象赋初值
    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
        // 在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");

        System.out.println("Method:" + method);

        // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);

        // 在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");

        return null;
    }

}

最后,来看看我们的Client类:

package com.dxz.proxy1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        // 我们要代理的真实对象
        Subject realSubject = new RealSubject();

        // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        InvocationHandler handler = new DynamicProxy(realSubject);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数 第一个参数
         * handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,
         * 表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了 第三个参数handler, 我们这里将这个代理对象关联到了上方的
         * InvocationHandler 这个对象上
         */
        Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(), handler);

        System.out.println(subject.getClass().getName());
        subject.rent();
        subject.hello("world");
    }
}

我们先来看看控制台的输出:

com.sun.proxy.$Proxy0
before rent house
Method:public abstract void com.dxz.proxy1.Subject.rent()
I want to rent my house
after rent house
before rent house
Method:public abstract void com.dxz.proxy1.Subject.hello(java.lang.String)
hello: world
after rent house

我们首先来看看 $Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?

Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

可能我以为返回的这个代理对象会是Subject类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号

接着我们来看看这两句 

subject.rent();
subject.hello("world");

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行:

    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
        // 在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before rent house");

        System.out.println("Method:" + method);

        // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);

        // 在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after rent house");

        return null;
    }

我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:

public abstract void com.dxz.proxy1.Subject.rent()

public abstract void com.dxz.proxy1.Subject.hello(java.lang.String)

正好就是我们的Subject接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的

JDK动态代理原理(JDK采用字节重组,重新生成对象来替代原始对象,以达到动态代理的目的)

JDK动态代理生成对象的步骤如下:

  1. 拿到被代理对象的引用,然后获取他的接口 (Proxy.getInstance方法) ,反射获取
  2. JDK代理重新生成一个类,同时该类实现我们给的代理对象所实现的接口 
  3. 动态生成java代码,新加的业务逻辑方法由一定的逻辑代码调用(在代码中体现)
  4. 编译新生成java代码的class文件
  5. 重新加载到JVM中运行

以上的过程就叫字节码重组。JDK中有个规范,只要要是$开头的.class文件,一般都是自动生成的。那么我们有没有办法看到代替后的对象的“真容”呢?做一个这样测试,我们将内存中的对象字节码通过文件输出到一个新的.class文件,然后利用反编译工具查看其源代码。

修改一下上面的client代码,把动态代理类输出到一个class文件里:

        subject.rent();
        subject.hello("world");
        
        // 这里我们将jdk生成的代理类输出了出来,方便后面分析使用
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{realSubject.getClass()});
 
        FileOutputStream os = new FileOutputStream("D:\\Proxy0.class");
        os.write(bytes);
        os.close();
    }

将Proxy0.class文件反编译:

 打开它可以看到如下内容:

import com.dxz.proxy1.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0
  extends Proxy
  implements Subject
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m4;
  private static Method m0;
  
  public $Proxy0(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 rent()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    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);
    }
  }
  
  public final void hello(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);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("com.dxz.proxy1.Subject").getMethod("rent", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m4 = Class.forName("com.dxz.proxy1.Subject").getMethod("hello", new Class[] { Class.forName("java.lang.String") });
      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());
    }
  }
}

说明:

  1. $Proxy0继承了Proxy类,同时还实现了Subject接口,而且重写了hello等业务方法。
  2. 在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,重写的方法中用反射调用目标对象的方法。

 

动态代理内部实现

首先来看看类Proxy的代码实现 Proxy的主要静态变量

// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map loaderToCache = new WeakHashMap(); 

// 标记:用于标记一个动态代理类正在被创建中
private static Object pendingGenerationMarker = new Object(); 

// 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断
private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); 

// 关联的调用处理器引用
protected InvocationHandler h;

Proxy的构造方法

// 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
private Proxy() {} 

// 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
protected Proxy(InvocationHandler h) {this.h = h;} 

 

总结

一个典型的动态代理创建对象过程可分为以下四个步骤:
1、通过实现InvocationHandler接口创建自己的调用处理器 IvocationHandler handler = new InvocationHandlerImpl(...);
2、通过为Proxy类指定ClassLoader对象和一组interface创建动态代理类
Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});
3、通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
4、通过构造函数创建代理类实例,此时需将调用处理器对象作为参数被传入
Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));
为了简化对象创建过程,Proxy类中的newInstance方法封装了2~4,只需两步即可完成代理对象的创建。
生成的ProxySubject继承Proxy类实现Subject接口,实现的Subject的方法实际调用处理器的invoke方法,而invoke方法利用反射调用的是被代理对象的的方法(Object result=method.invoke(proxied,args))

美中不足

诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。有很多条理由,人们可以否定对 class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。接口和类的划分,本就不是很明显,只是到了Java中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java动态代理就是佐例。

cglib

jdk给目标类提供动态要求目标类必须实现接口,当一个目标类不实现接口时,jdk是无法为其提供动态代理的。cglib 却能给这样的类提供动态代理。

Spring AOP

详细见:Spring AOP 实现原理

拦截器

struts2拦截器的实现原理及源码剖析

 
posted on 2012-12-03 13:52  duanxz  阅读(5442)  评论(1编辑  收藏  举报