JDK动态代理

一、基本概念

1.什么是代理?

在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念。代理这个词本身并不是计算机专用术语,它是生活中一个常用的概念。这里引用维基百科上的一句话对代理进行定义:
A proxy is an agent or substitute authorized to act for another person or a document which authorizes the agent so to act.
意思是说:代理指的是一个代理人(或替代品),它被授权代表另外一个人(或文档)。
从这个简明扼要的定义中,可以看出代理的一些特性:1.代理存在的意义就是代表另一个事物。2.代理至少需要完成(或实现)它所代表的事物的功能。

人与人之间的代理

2.什么是JAVA静态代理?

JAVA静态代理是指由程序员创建或工具生成的代理类,这个类在编译期就已经是确定了的,存在的。
典型的静态代理模式一般包含三类角色:
1.抽象角色:它的作用是定义一组行为规范。抽象角色一般呈现为接口(或抽象类),这些接口(或抽象类)中定义的方法就是待实现的。
2.真实角色:实现了抽象角色所定义的行为。真实角色就是个普通的类,它需要实现抽象角色定义的那些接口。
3.代理角色:代表真实角色的角色。根据上面代理的定义,我们可以知道代理角色需要至少完成(或实现)真实角色的功能。为了完成这一使命,那么代理角色也需要实现抽象角色所定义的行为(即代理类需要实现抽象角色所定义的接口),并且在实现接口方法的时候需要调用真实角色的相应方法。

静态代理

上图使用UML类图解释了静态代理的数据模型。
1.接口IFunc代表了抽象角色,定义了一个行为,即方法doSomething()。
2.类RealFunc代表了真实角色,它实现了IFunc接口中定义的方法doSomething()。
3.类ProxyFunc代表了代理角色,它实现了IFunc接口中定义的方法doSomething()。它的实现方式是依赖RealFunc类的,通过持有RealFunc类对象的引用realObj,在ProxyFunc.doSomething()方法中调用了realObj.doSomething()。当然,代理类也可以做一些其他的事情,如图中的doOtherthing()。

通过上面的介绍,可以看出静态代理存在以下问题:
1.代理类依赖于真实类,因为代理类最根本的业务功能是需要通过调用真实类来实现的。那么如果事先不知道真实类,该如何使用代理模式呢?
2.一个真实类必须对应一个代理类,即当有多个真实类RealA、RealB、RealC...的时候,就需要多个代理类ProxyA、ProxyB、ProxyC...。这样的话如果大量使用静态代理,容易导致类的急剧膨胀。该如何解决?

要想解决上述问题,就需要使用下面讲解的JAVA动态代理。

3.什么是JAVA动态代理?

JAVA动态代理与静态代理相对,静态代理是在编译期就已经确定代理类和真实类的关系,并且生成代理类的。而动态代理是在运行期利用JVM的反射机制生成代理类,这里是直接生成类的字节码,然后通过类加载器载入JAVA虚拟机执行。现在主流的JAVA动态代理技术的实现有两种:一种是JDK自带的,就是我们所说的JDK动态代理,另一种是开源社区的一个开源项目CGLIB。本文主要对JDK动态代理做讨论。

4.什么是JDK动态代理?

JDK动态代理的实现是在运行时,根据一组接口定义,使用Proxy、InvocationHandler等工具类去生成一个代理类和代理类实例。

JDK动态代理的类关系模型和静态代理看起来差不多。也是需要一个或一组接口来定义行为规范。需要一个代理类来实现接口。区别是没有真实类,因为动态代理就是要解决在不知道真实类的情况下依然能够使用代理模式的问题。

JDK动态代理

图中高亮显示的$Proxy0即为JDK动态代理技术生成的代理类,类名的生成规则是前缀"$Proxy"加上一个序列数。这个类继承Proxy,实现一系列的接口Intf1,Intf2...IntfN。

既然要实现接口,那么就要实现接口的各个方法,即图中的doSomething1(),doSomething2()...doSomethingN()。我们上面介绍静态代理的时候,知道静态代理类本质上是调用真实类去实现接口定义的方法的。但是JDK动态代理中是没有真实类这样的概念的。那么JDK动态代理类是如何实现这些接口方法的具体逻辑呢?答案就在图中的InvocationHandler上。$Proxy0对外只提供一个构造函数,这个构造函数接受一个InvocationHandler实例h,这个构造函数的逻辑非常简单,就是调用父类的构造函数,将参数h赋值给对象字段h。最终就是把所有的方法实现都分派到InvocationHandler实例h的invoke方法上。所以JDK动态代理的接口方法实现逻辑是完全由InvocationHandler实例的invoke方法决定的。

二、样例分析

了解了JDK动态代理的概念后,现在我们动手写个JDK动态代理的代码样例。直观的认识下JDK动态代理技术为我们的做了什么。上面说到了,JDK动态代理主要依靠Proxy和InvocationHandler这两个类来生成动态代理类和类的实例。这两个类都在jdk的反射包java.lang.reflect下面。Proxy是个工具类,有了它就可以为接口生成动态代理类了。如果需要进一步生成代理类实例,需要注入InvocationHandler实例。这点我们上面解释过,因为代理类最终逻辑的实现是分派给InvocationHandler实例的invoke方法的。

闲话休絮,先开始我们的第一步,定义一个接口。这个接口里面定义一个方法helloWorld()。

public interface MyIntf {
    void helloWorld();
}

第二步,编写一个我们自己的调用处理类,这个类需要实现InvocationHandler接口。

public class MyInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method);
        return null;
    }
}

InvocationHandler接口只有一个待实现的invoke方法。这个方法有三个参数,proxy表示动态代理类实例,method表示调用的方法,args表示调用方法的参数。在实际应用中,invoke方法就是我们实现业务逻辑的入口。这里我们的实现逻辑就一行代码,打印当前调用的方法(在实际应用中这么做是没有意义的,不过这里我们只想解释JDK动态代理的原理,所以越简单越清晰)。

第三步,直接使用Proxy提供的方法创建一个动态代理类实例。并调用代理类实例的helloWorld方法,检测运行结果。

public class ProxyTest {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        MyIntf proxyObj = (MyIntf)Proxy.newProxyInstance(MyIntf.class.getClassLoader(),new Class[]{MyIntf.class},new MyInvocationHandler());
        proxyObj.helloWorld();
    }
}

第三行代码是设置系统属性,把生成的代理类写入到文件。这里再强调一下,JDK动态代理技术是在运行时直接生成类的字节码,并载入到虚拟机执行的。这里不存在class文件的,所以我们通过设置系统属性,把生成的字节码保存到文件,用于后面进一步分析。
第四行代码就是调用Proxy.newProxyInstance方法创建一个动态代理类实例,这个方法需要传入三个参数,第一个参数是类加载器,用于加载这个代理类。第二个参数是Class数组,里面存放的是待实现的接口信息。第三个参数是InvocationHandler实例。
第五行调用代理类的helloWorld方法,运行结果:

public abstract void com.tuniu.distribute.openapi.common.annotation.MyIntf.helloWorld()

分析运行结果,就可以发现,方法的最终调用是分派到了MyInvocationHandler.invoke方法,打印出了调用的方法信息。

到这里,对于JDK动态代理的基本使用就算讲完了。我们做的事情很少,只是编写了接口MyIntf和调用处理类MyInvocationHandler。其他大部分的工作都是Proxy工具类帮我们完成的。Proxy帮我们创建了动态代理类和代理类实例。上面的代码我们设置了系统属性,把生成的字节码保存到class文件。下面我们通过反编译软件(如jd-gui),看下Proxy类为我们生成的代理类是什么样子的。

package com.sun.proxy;
import com.tuniu.distribute.openapi.common.annotation.MyIntf;
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 MyIntf {
    private static Method m0;
    private static Method m1;
    private static Method m2;
    private static Method m3;

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.tuniu.distribute.openapi.common.annotation.MyIntf").getMethod("helloWorld", new Class[0]);
            return;
        } catch (NoSuchMethodException localNoSuchMethodException) {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        } catch (ClassNotFoundException localClassNotFoundException) {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }

    public $Proxy0(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }

    public final void helloWorld() {
        try {
            this.h.invoke(this, m3, null);
            return;
        } catch (Error | RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    // 后面省略equals(),hashCode(),toString()三个方法的代码,因为这三个方法和helloWorld()方法非常相似
}

这里Proxy为我们生成的代理类叫$Proxy0,继承了Proxy,实现了我们定义的接口MyIntf。每一个JDK动态代理技术生成的代理类的名称都是由$Proxy前缀加上一个序列数0,1,2...。并且都需要继承Proxy类。

$Proxy0类中9-26行代码定义了4个Method字段m0,m1,m2,m3,我们先来看下m3,它描述了我们定义的接口MyIntf中的方法helloWorld。

紧接着下面的32-41行代码就是对helloWorld方法的实现,它的实现非常简单就一句话this.h.invoke(this, m3, null);这行代码就是调用当前对象的h实例的invoke方法,也就是把方法的实现逻辑分派给了h.invoke。这里的h是继承父类Proxy中的InvocationHandler字段(读者可以结合上面的动态代理类图模型或者Proxy源码进一步理解)。

同时$Proxy0提供了一个构造函数(代码28-30行),调用父类的构造函数来注入这个InvocationHandler实例。

$Proxy0中的另外3个Method对象m0,m1,m2分别代表了Object类的hashCode(),equals(),toString()方法,我们知道java中的所有类都是Object的子类(Object类本身除外),这里$Proxy0重写了Object中的这三个方法。这三个方法的实现和helloWorld方法很类似,所以笔者这里就把这段代码省略了,用一行注释(43行代码)解释了下。

行文至此,我们已经感官的认识了运行时生成的代理类结构。揭开了这层面纱,其实JDK动态代理也没什么了。简单的来说就是,JDK动态代理技术可以为一组接口生成代理类,这个代理类也就是一层壳,简单的实现了接口中定义的方法。通过提供一个构造函数传入InvocationHandler实例,然后将方法的具体实现交给它。

三、源码分析

前两部分分别从概念和应用的角度阐述了JDK动态代理技术。最后一部分我们将从Proxy源码对JDK动态代理进行深入的剖析。Proxy类对外提供了4个静态方法,分别为:

public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces);
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
public static boolean isProxyClass(Class<?> cl);
public static InvocationHandler getInvocationHandler(Object proxy);

下面我们通过各个方法的源码依次分析。

1.getProxyClass

getProxyClass方法返回代理类的Class实例。这个代理类就是类加载器loader定义的、实现了一些列接口interfaces的。如果之前已经为这个loader和interfaces创建过代理类,那么直接返回这个代理类的Class实例。如果没有,则动态创建并返回。

public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException {
    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    return getProxyClass0(loader, intfs);
}

getProxyClass方法并没有JDK动态代理的核心逻辑:第二行将接口的Class数组interfaces进行克隆。3-6行是类加载器和接口访问权限的校验(这里虚拟机的安全性相关逻辑,不是我们JDK代理技术的关注点,所以不做过多解释)。关键的逻辑就最后一行代码,调用getProxyClass0方法去获取代理类的Class实例。

private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    return proxyClassCache.get(loader, interfaces);
}

getProxyClass0也没有包含JDK动态代理的核心逻辑:2-4行只是对接口的个数进行了简单的校验,不能超过65535,我们在实际应用中一般也不会出现这种情况。最后一行代码是去缓存对象proxyClassCache中获取代理类的Class实例。proxyClassCache是Proxy类的静态变量

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

proxyClassCache是类java.lang.reflect.WeakCache的实例,通过类名就可以看出来这个类是用来做缓存的。Proxy调用WeakCache提供的构造函数,传入KeyFactory实例和ProxyClassFactory实例(这两个实例的用途后面会讲到)。在分析WeakCache的get方法源码之前,我们先来大概介绍下WeakCache缓存的数据结构。

代理的数据结构

WeakCache缓存的数据结构是(key,sub-key)->(value)。这个结构和Redis里面的hash结构很类似,根据一级的键(key)、二级的键(sub-key)为索引,去检索出值(value)。对应到WeakCache类代码里面,就是一个ConcurrentMap实例map,这个map的key就是一级键,map的value又是个ConcurrentMap实例,这个子map的key是二级键,子map的value就是缓存的的值。上面图中的箭头就表示着对应关系,一目了然。图中下半部分是JDK动态代理缓存的键值生成规则,后面会一一详解。下面我们看下WeakCache的get方法源码。

public V get(K key, P parameter) {
    Objects.requireNonNull(parameter);
    expungeStaleEntries();
    Object cacheKey = CacheKey.valueOf(key, refQueue);
    ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
    if (valuesMap == null) {
        ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>());
        if (oldValuesMap != null) {
            valuesMap = oldValuesMap;
        }
    }
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
    Factory factory = null;
    while (true) {
        if (supplier != null) {
            V value = supplier.get();
            if (value != null) {
                return value;
            }
        }
        if (factory == null) {
            factory = new Factory(key, parameter, subKey, valuesMap);
        }
        if (supplier == null) {
            supplier = valuesMap.putIfAbsent(subKey, factory);
            if (supplier == null) {
                supplier = factory;
            }
        } else {
            if (valuesMap.replace(subKey, supplier, factory)) {
                supplier = factory;
            } else {
                supplier = valuesMap.get(subKey);
            }
        }
    }
}

我们的调用语句是proxyClassCache.get(loader, interfaces),也就是说形参key是类加载器loader,parameter是接口的Class数组interfaces。

第2行代码是对形参parameter(interfaces)进行非空的校验,如果为空则抛出空指针异常。第3行代码是去除缓存中的陈旧数据,这里不是我们的关注点,就不详细介绍了。

第4行是根据形参key(loader)计算出缓存的一级键cacheKey,这里我们不去看具体的生成逻辑,只需要大概知道一级键是根据形参key(loader)算出来的,这里可以用一个数学函数表达式描述这个关系:key=f(loader)。

第5行代码是根据一级键查出值,这个值的Map实例valuesMap。由于之前没有为这个loader和interfaces创建过代理类,所以valuesMap为null,6-11行代码会被执行,这几行代码就是给valueMap一个初始值,然后结合上面算出来的一级键cacheKey塞进缓存实例map里面。

第12行根据key(loader)和parameter(interfaces)计算出缓存的二级键subKey。这里的subKeyFactory是Proxy调用WeakCache提供的构造函数时,传入的KeyFactory实例(上面提到过)。KeyFactory是Proxy的内部类,我们简单看下KeyFactory的apply方法,看下是怎么生成二级键的。

public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
    switch (interfaces.length) {
        case 1: return new Key1(interfaces[0]);
        case 2: return new Key2(interfaces[0], interfaces[1]);
        case 0: return key0;
        default: return new KeyX(interfaces);
    }
}

这里笔者不打算展开分析每行代码,我们通过上面的代码,只需要大概知道二级键是根据interfaces计算出来的(classLoader这个参数根本没用到)。这里可以用一个数学函数表达式描述这个关系:sub-key=g(interfaces)。

我们继续上面的WeakCache.get方法分析,第13行代码是根据二级键subKey从valuesMap获取值supplier,这个值supplier也就是我们缓存数据的值。由于valuesMap是新建的,所以supplier为null。

15-37行是个循环,第一次进入的时候,factory和supplier都为null。所以22-30行代码将被执行。第23行代码是调用Factory构造函数创建一个实例factory(Factory是WeakCache的内部类),这个构造函数就是简单把传入的参数赋值给factory实例的字段。接下来26-29将构造的二级键subKey和factory塞进valuesMap,并将factory赋给supplier(Factory类继承Supplier类)。到这里缓存数据的初始化就算告一段落了,一级键是根据loader计算出来的cacheKey,二级键是根据interfaces计算出来的subKey,值是new的一个factory(Supplier实例)。

第一遍的循环并没有创建代理类,只是做了一些初始化的工作,下面继续执行这个循环体(15-37行)。这次supplier不为null了(就是上面的Factory实例factory),所以进入16-21行的代码块,第17行实质就是调用factory.get方法。这个方法返回的value也就是动态代理类的Class实例。紧接着第19行就把这个value返回。下面来看下Factory的get方法的源码。

public synchronized V get() {
    Supplier<V> supplier = valuesMap.get(subKey);
    if (supplier != this) {
        return null;
    }
    V value = null;
    try {
        value = Objects.requireNonNull(valueFactory.apply(key, parameter));
    } finally {
        if (value == null) {
            valuesMap.remove(subKey, this);
        }
    }
    assert value != null;
    CacheValue<V> cacheValue = new CacheValue<>(value);
    if (valuesMap.replace(subKey, this, cacheValue)) {
        reverseMap.put(cacheValue, Boolean.TRUE);
    } else {
        throw new AssertionError("Should not reach here");
    }
    return value;
}

第2行代码是根据二级键subKey得到值supplier,也就是我们在上面的WeakCache的get方法中创建的Factory实例factory。

接下来的几行代码没什么好讲的,直接看第8行代码,这行代码调用了valueFactory.apply方法创建动态代理类并将结果赋值给变量value。

9-14行针对创建代理类失败的情况下做的处理和判断逻辑。如果创建代理类成功,则继续执行后面的代码。

第15行代码把生成的代理类的Class实例(即value变量)进行缓存。缓存的值并不是直接的value,而是由value构造的一个CacheValue实例cacheValue,由于CacheValue实现了接口Value,而Value接口继承了Supplier接口,所以cacheValue就是Supplier的实例。这里我们不需要去深究CacheValue的数据结构,只需要知道缓存的值是根据代理类的Class实例去计算的,这里可以用一个数学函数表达式描述这个关系:value=h($ProxyX.class)。

第16行代码将二级键subKey和cacheValue放入valuesMap(valuesMap的类型是ConcurrentMap<Object, Supplier<V>>)。第18行是记录缓存状态的。方法的最后将代理类的Class实例value返回。

这个方法的主要逻辑是对缓存的操作,动态代理类的创建动作是通过调用valueFactory.apply得到的。这里的valueFactory是在构造WeakCache时传入的参数,上面提到的ProxyClassFactory实例。由于Factory是WeakCache的内部类,所以在Factory的get方法中可以使用这个实例valueFactory。下面我们就来看下ProxyClassFactory的apply方法。

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
    for (Class<?> intf : interfaces) {
        Class<?> interfaceClass = null;
        try {
            interfaceClass = Class.forName(intf.getName(), false, loader);
        } catch (ClassNotFoundException e) {}
        if (interfaceClass != intf) {
            throw new IllegalArgumentException(intf + " is not visible from class loader");
        }
        if (!interfaceClass.isInterface()) {
            throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");
        }
        if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
            throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());
        }
    }
    String proxyPkg = null;
    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");
            }
        }
    }
    if (proxyPkg == null) {
        proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
    }
    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());
    }
}

第2-17行代码,是几个简单的校验工作:1.接口interfaces是否对类加载器loader可见(即接口interfaces是由类加载器loader加载的或者由loader的上一级类加载器加载的,这点需要读者了解JVM的类加载知识),2.参数interfaces是否都是接口,3.参数interfaces里面有没有重复数据。

第18-26行代码,是为即将生成的代理类计算出访问标志和包名。具体的约束和规则如下:

1.如果所有的接口的访问标志都是public,那么生成的代理类的访问标志是final public,否则是final。

2.对于访问标志不是public的接口,它们必须要在同一个包下,否则抛出异常。

3.如果存在访问标志不是public的接口,那么生成的代理类的包名就是这些接口的包名。否则包名是默认的ReflectUtil.PROXY_PACKAGE(即com.sun.proxy)。

第37-38行代码,是计算代理类的权限定名。代理类的简单名称生成规则前面介绍过,是特定前缀"$Proxy"加上一个序列数(0,1,2,3...)。

第39行调用ProxyGenerator的静态方法generateProxyClass去创建代理类的字节码。这个方法我们就不跟进去看了,因为它的逻辑就是根据一些固化的规则(比如代理类里面要实现接口的方法,实现Object的equals、hashCode、toString方法,提供一个形参为InvocationHandler实例的构造函数等等),依据JAVA虚拟机规范中定义的Class类文件结构去生成字节码的。我们之前在第二部分“样例分析”中通过反编译软件,观察分析过生成的代理类结构。这里读者可以和前面的内容融会贯通一下。

第41行代码,调用Proxy的本地方法defineClass0将生成的代理类字节码加载到虚拟机,并返回代理类的Class实例。(如果读者对类加载的只是感兴趣的话,可以去深入学习下JAVA虚拟机的类加载机制)

到目前为止,JDK动态代理类的创建流程就全部结束了,我们说的是首次创建代理类的情况,现在我们回头来看下WeakCache的get方法,如果之前已经为类加载器loader和接口interfaces创建过了代理类,那么调用这个方法的时候是个什么样子呢?答案就是根据一级键和二级键直接从缓存中取到代理类的Class实例。这里就不再逐行分析代码了,读者自己理解下。最后用一张简单概要的时序图描绘一下Proxy的getProxyClass0方法生成代理类的调用流程。

时序图

2.newProxyInstance

有了上面对动态代理类的创建过程的系统理解,现在来看newProxyInstance方法就容易多了,它就是使用反射机制调用动态代理类的构造函数生成一个代理类实例的过程。

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);
    }
}

第2-7行代码,克隆interfaces,并进行权限相关校验,和前面getProxyClass方法类似,这里不做赘述。
第8行getProxyClass0方法获取代理类的Class实例,这个方法在上面也详细介绍过了。
第10-12行也是进行权限校验,检查调用者是否对这个代理类有访问权限。
第13-23行就是构造代理类实例的过程。先获取代理类的构造函数,接着对其访问权限进行判断,如果不是public,则将其设置可访问的。最后利用反射机制调用构造方法,传入参数InvocationHandler的实例h,创建代理类实例并返回。

3.isProxyClass

这个方法用于判断一个类是不是代理类。

public static boolean isProxyClass(Class<?> cl) {
    return Proxy.class.isAssignableFrom(cl) && proxyClassCache.containsValue(cl);
}

Proxy.class.isAssignableFrom(cl)这句话简而言之就是判断cl所表示的类是不是Proxy或者Proxy的子类。因为所有的代理类都集成Proxy,所以这个条件必须满足。满足这个条件也不能保证就是代理类,因为可能存在人为地编写一个类继承Proxy这种情况。proxyClassCache.containsValue(cl)这个方法是检查缓存中是否存在这个Class实例cl。我前面分析过,但凡生成的代理类都会被缓存,所以这个方法才是检测一个类是否是代理类的唯一标准。

4.getInvocationHandler

这个方法用于获取代理类中的InvocationHandler实例。这个方法没有什么太多的逻辑,基本就是判断下传入的对象是否是代理类,以及一些访问权限的校验。当这些都合法的情况下,返回InvocationHandler实例。

public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException {
    if (!isProxyClass(proxy.getClass())) {
        throw new IllegalArgumentException("not a proxy instance");
    }
    final Proxy p = (Proxy) proxy;
    final InvocationHandler ih = p.h;
    if (System.getSecurityManager() != null) {
        Class<?> ihClass = ih.getClass();
        Class<?> caller = Reflection.getCallerClass();
        if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(),ihClass.getClassLoader())) {
            ReflectUtil.checkPackageAccess(ihClass);
        }
    }
    return ih;
}

至此,关于JDK动态代理的技术都全部讲解完了。本文从基本概念、样例分析、源码分析三个角度去分析说明JDK动态代理背后的知识,希望对读者有所帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容