关闭

java 动态代理实现原理

标签: 动态代理实现原理
1875人阅读 评论(0) 收藏 举报
分类:


上篇讲了:java动态代理浅析  这篇讲讲其内部实现原理。


1、相关的类和接口

1.1 java.lang.reflect.Proxy

这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

Proxy 的静态方法:

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy) 


// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces) 


// 方法 3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl) 


// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上


1.2 java.lang.reflect.InvocationHandler

这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

// 该方法负责集中处理动态代理类上的所有方法调用   
Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数</span>


2、动态代理实现过程

具体有如下步骤:(最终目的得到动态代理类的实例对象

1.创建自己的调用处理器:通过实现 InvocationHandler 接口创建自己的调用处理器;

2.创建动态代理类的类对象:通过为 Proxy 类指定 ClassLoader 对象和一组 interface 以及调用处理器来创建动态代理类;

3.获得动态代理类的构造函数通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;

4.创建动态代理类实例通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。


动态代理对象创建过程:

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 

// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 

// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下:

简化的动态代理对象创建过程

/ InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );


3、Java 动态代理机制的一些特点

3.1动态生成的代理类本身的一些特点

3.1.1包

如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protect 或 private,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包(假设代理了 com.ibm.developerworks 包中的某非 public 接口 A,那么新生成的代理类所在的包就是 com.ibm.developerworks),这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;

3.1.2类修饰符

该代理类具有 final 和 public 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;

3.1.3类名

格式是“$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。

3.1.4类继承关系

该类的继承关系如图:


由图可见,Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。

3.2 代理类实例的一些特点

3.2.1每个实例都会关联一个调用处理器对象

可以通过 Proxy 提供的静态方法 getInvocationHandler去获得代理类实例的调用处理器对象。

3.2.2 代理类实例的方法

在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行,它们是 hashCode,equals 和 toString

可能的原因有:

一是因为这些方法为 public 且非 final 类型,能够被代理类覆盖;

二是因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。

当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。

3.2.3 被代理的一组接口有哪些特点

首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。

其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。

再次,需被代理的所有非 public 的接口必须在同一个包中,否则代理类生成也会失败。

最后,接口的数目不能超过 65535,这是 JVM 设定的限制。

3.2.4 异常处理方面的特点

从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于 Throwable 接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Java 动态代理类已经为我们设计好了解决方法:它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型,所以不会引起编译错误。通过该异常的 getCause 方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。


4、动态代理源码分析

4.1 简单的动态代理实现

package dynamic.proxy;  
  
/** 
 * 目标对象实现的接口,用JDK来生成代理对象一定要实现一个接口 
 */  
public interface UserService {  
  
    /** 
     * 目标方法  
     */  
    public abstract void add();  
  
}


package dynamic.proxy;   
  
/** 
 * 目标对象 
 */  
public class UserServiceImpl implements UserService {  
  
    public void add() {  
        System.out.println("--------------------add---------------");  
    }  
} 

package dynamic.proxy;   
  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
import java.lang.reflect.Proxy;  
  
/** 
 * 实现自己的InvocationHandler 
 */  
public class MyInvocationHandler implements InvocationHandler {  
      
    // 目标对象   
    private Object target;  
      
    /** 
     * 构造方法 
     * @param target 目标对象  
     */  
    public MyInvocationHandler(Object target) {  
        super();  
        this.target = target;  
    }  
  
  
    /** 
     * 执行目标对象的方法 
     */  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
          
        // 在目标对象的方法执行之前简单的打印一下  
        System.out.println("------------------before------------------");  
          
        // 执行目标对象的方法  
        Object result = method.invoke(target, args);  
          
        // 在目标对象的方法执行之后简单的打印一下  
        System.out.println("-------------------after------------------");  
          
        return result;  
    }  
  
    /** 
     * 获取目标对象的代理对象 
     * @return 代理对象 
     */  
    public Object getProxy() {  
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),   
                target.getClass().getInterfaces(), this);  
    }  
}  
执行结果如下: 
------------------before------------------ 
--------------------add--------------- 
-------------------after------------------  

待弄清楚问题:

1.代理对象是怎么生成的
2.InvocationHandler的invoke方法是由谁来调用的


4.2 Proxy类

Proxy 的重要静态变量

// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map<ClassLoader, Map<List<String>, Object>> 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;}


Proxy 静态方法 newProxyInstance:

public static Object newProxyInstance(ClassLoader loader, 
            Class<?>[] interfaces, 
            InvocationHandler h) 
            throws IllegalArgumentException { 
    
    // 检查 h 为空,否则抛异常
    if (h == null) { 
        throw new NullPointerException(); 
    } 

    // 获得与制定类装载器和一组接口相关的代理类类型对象
    Class cl = getProxyClass(loader, interfaces); 

    // 通过反射获取构造函数对象并生成代理类实例
    try { 
        // 调用代理对象的构造方法(也就是$Proxy0(InvocationHandler h))
        Constructor cons = cl.getConstructor(constructorParams); 
        // 生成代理类的实例并把MyInvocationHandler的实例传给它的构造方法
        return (Object) cons.newInstance(new Object[] { h }); 
    } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
    } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
    } catch (InstantiationException e) { throw new InternalError(e.toString()); 
    } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
    } 
}

由此可见,动态代理真正的关键是在 getProxyClass 方法,该方法负责为一组接口动态地生成代理类Class(类型)对象。


Proxy 静态方法 getProxyClass:

该方法总共可以分为四个步骤

1.对这组接口进行一定程度的安全检查

包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。这个步骤通过一个循环来完成,检查通过后将会得到一个包含所有接口名称的字符串数组,记为 String[] interfaceNames。总体上这部分实现比较直观,所以略去大部分代码,仅保留留如何判断某类或接口是否对特定类装载器可见的相关代码。

通过 Class.forName 方法判接口的可见性:

try { 
    // 指定接口名字、类装载器对象,同时制定 initializeBoolean 为 false 表示无须初始化类
    // 如果方法返回正常这表示可见,否则会抛出 ClassNotFoundException 异常表示不可见
    interfaceClass = Class.forName(interfaceName, false, loader); 
} catch (ClassNotFoundException e) { 
}

2.从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。

缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。

private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache = new WeakHashMap<>();

ClassLoader:类装载器

Map<List<String>, Object>:List<String> --- 接口名字列表  Object---动态生成的代理类的类对象引用


缓存表的使用:

do { 
    // 以接口名字列表作为关键字获得对应 cache 值
    Object value = cache.get(key); 
    if (value instanceof Reference) { 
        proxyClass = (Class) ((Reference) value).get(); 
    } 
    if (proxyClass != null) { 
        // 如果已经创建,直接返回
        return proxyClass; 
    } else if (value == pendingGenerationMarker) { 
        // 代理类正在被创建,保持等待
        try { 
            cache.wait(); 
        } catch (InterruptedException e) { 
        } 
        // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
        continue; 
    } else { 
        // 标记代理类正在被创建
        cache.put(key, pendingGenerationMarker); 
        // break 跳出循环已进入创建过程
        break; 
} while (true);

3.动态创建代理类的Class(类)对象

1.首先是确定代理类所在的包,其原则如前所述,如果都为 public 接口,则包名为空字符串表示顶层包;如果所有非 public 接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public 接口且不同包,则抛异常终止代理类的生成。

2.确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。

3.类名也确定了,接下来就是见证奇迹的发生 ——动态生成代理类 (解决了4.1中待弄清楚问题1)

动态生成代理类

// 动态地生成代理类的字节码数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); 
try { 
    // 动态地定义新生成的代理类
    proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, 
        proxyClassFile.length); 
} catch (ClassFormatError e) { 
    throw new IllegalArgumentException(e.toString()); 
} 

// 把生成的代理类的类对象记录进 proxyClasses 表
proxyClasses.put(proxyClass, null);

由此可见,所有的代码生成的工作都由神秘的 ProxyGenerator 所完成了,当你尝试去探索这个类时,你所能获得的信息仅仅是它位于并未公开的 sun.misc 包,有若干常量、变量和方法以完成这个神奇的代码生成的过程,但是 sun 并没有提供源代码以供研读。至于动态类的定义,则由 Proxy 的 native 静态方法 defineClass0 执行。

4.根据结果更新缓存表

如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。


JDK是怎样动态生成代理类的字节的原理已经一目了然了。再来解决另外一个问题,那就是由谁来调用InvocationHandler的invoke方法的。要解决这个问题就要看一下JDK到底为我们生成了一个什么东西。我们可以import sun.misc.ProxyGenerator,调用 generateProxyClass方法产生binary data,然后写入文件,最后通过反编译工具来查看内部实现原理。

import dynamic.proxy.UserService;  
import java.lang.reflect.*;  
  
public final class $Proxy11 extends Proxy  
    implements UserService  
{  
  
    // 构造方法,参数就是刚才传过来的MyInvocationHandler类的实例  
    public $Proxy11(InvocationHandler invocationhandler)  
    {  
        super(invocationhandler);  
    }  
  
    public final boolean equals(Object obj)  
    {  
        try  
        {  
            return ((Boolean)super.h.invoke(this, m1, new Object[] {  
                obj  
            })).booleanValue();  
        }  
        catch(Error _ex) { }  
        catch(Throwable throwable)  
        {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    /** 
     * 这个方法是关键部分 
     */  
    public final void add()  
    {  
        try  
        {  
            // 实际上就是调用MyInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二个问题就解决了  
            super.h.invoke(this, m3, null);  
            return;  
        }  
        catch(Error _ex) { }  
        catch(Throwable throwable)  
        {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    public final int hashCode()  
    {  
        try  
        {  
            return ((Integer)super.h.invoke(this, m0, null)).intValue();  
        }  
        catch(Error _ex) { }  
        catch(Throwable throwable)  
        {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    public final String toString()  
    {  
        try  
        {  
            return (String)super.h.invoke(this, m2, null);  
        }  
        catch(Error _ex) { }  
        catch(Throwable throwable)  
        {  
            throw new UndeclaredThrowableException(throwable);  
        }  
    }  
  
    private static Method m1;  
    private static Method m3;  
    private static Method m0;  
    private static Method m2;  
  
    // 在静态代码块中获取了4个方法:Object中的equals方法、UserService中的add方法、Object中的hashCode方法、Object中toString方法  
    static   
    {  
        try  
        {  
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {  
                Class.forName("java.lang.Object")  
            });  
            m3 = Class.forName("dynamic.proxy.UserService").getMethod("add", new Class[0]);  
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);  
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);  
        }  
        catch(NoSuchMethodException nosuchmethodexception)  
        {  
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
        }  
        catch(ClassNotFoundException classnotfoundexception)  
        {  
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
        }  
    }  
}  


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



参考来源:

java的动态代理机制详解

Java 动态代理机制分析及扩展,第 1 部分

JDK动态代理实现原理

彻底理解JAVA动态代理




0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:74663次
    • 积分:1651
    • 等级:
    • 排名:千里之外
    • 原创:89篇
    • 转载:15篇
    • 译文:0篇
    • 评论:26条
    最新评论