前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java中动态代理机制

Java中动态代理机制

作者头像
Bug开发工程师
发布2020-02-13 12:28:00
5620
发布2020-02-13 12:28:00
举报
文章被收录于专栏:码农沉思录码农沉思录

前言

代理是基本的设计模式之一,它是你为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色。代理这种设计模式是通过不直接访问被代理对象的方式,而访问被代理对象的方法。

从Java面向对象编程的角度,代理模式通常也涉及三个角色:(1)目标类(TargetSubject),也就是具体实现业务功能的类;(2)代理类(ProxyObject),提供一些被代理类功能之外的额外功能的类;(3)请求类(RequestObject),即:业务调用类。

使用代理的情况:

(1)设计模式中有一个设计原则是开闭原则,是说对修改关闭对扩展开放,我们在工作中有时会接手很多前人的代码,里面代码逻辑让人摸不着头脑(sometimes the code is really like shit),这时就很难去下手修改代码,那么这时我们就可以通过代理对类进行增强。

(2)我们在使用RPC框架的时候,框架本身并不能提前知道各个业务方要调用哪些接口的哪些方法 。那么这个时候,就可用通过动态代理的方式来建立一个中间人给客户端使用,也方便框架进行搭建逻辑,某种程度上也是客户端代码和框架松耦合的一种表现。

(3)Spring的AOP机制就是采用动态代理的机制来实现切面编程。

动态代理的构成:接口类;实现接口类的目标类;实现了InvocationHandler接口的代理类。

在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。

动态代理和静态代理的区别:

静态代理中对每一个TargetObject都要生成一个对应的ProxyObject,这样会造成类膨胀,并且不利于统一处理。静态代理是在编译时就引入代理类,而动态代理则是在运行时才动态生成代理类。简单来说就是通过动态代理我们不再需要手动的去写一个一个的代理类了,而是在运行时动态代理技术自动帮我们生成这个代理类。

从上图也可以看出:TargetSubject和ProxyObject对外提供了统一的功能,那么结合Java的语言特性,要达到这样的效果,有两种方法:一是通过接口,TargetSubject和ProxyObject都实现同样的接口;一是通过继承,ProxyObject必须继承TargetSubject。这两种方法也衍生出了Java中实现动态代理的两种方案:JDK动态代理和Cglib动态代理。

JDK动态代理

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

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

通过上图我们可以很清楚地看到JDK的动态代理机制实现的时候引入了一个公共的接口:InvocationHandler,而该接口由我们具体实现,并且要在具体的实现类中持有TargetSubject的引用。由此,我们可以猜想是由InvocationHandler的具体实现类,提供了具体的代理业务逻辑。并且需要指出的是,ProxyObject这个代理对象是在运行时生成的而不需要我们手动编写。

下面动手写个JDK动态代理的代码样例:

代码语言:javascript
复制
/**
 * @Description 代理测试接口
 */
 //第一步,定义一个接口。这个接口里面定义一个方法helloWorld()。
public interface MyIntf {
  void helloWorld();
}
代码语言:javascript
复制
/**
 * @Description 目标类
 */
 //第二步,编写一个我们自己的调用处理类,这个类需要实现InvocationHandler接口
//InvocationHandler接口只有一个待实现的invoke方法。
//这个方法有三个参数,proxy表示动态代理类实例,method表示调用的方法,args表示调用方法的参数。
//在实际应用中,invoke方法就是我们实现业务逻辑的入口。
//这里我们的实现逻辑就一行代码,打印当前调用的方法
//(在实际应用中这么做是没有意义的,不过这里我们只想解释JDK动态代理的原理,所以越简单越清晰
public class MyInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method);
        return null;
    }
}
代码语言:javascript
复制
/**
 * @Description 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();
    }
}

第8行代码是设置系统属性,把生成的代理类写入到文件。这里再强调一下,JDK动态代理技术是在运行时直接生成类的字节码,并载入到虚拟机执行的。这里不存在class文件的,所以我们通过设置系统属性,把生成的字节码保存到文件,用于后面进一步分析。

第9行代码就是调用Proxy.newProxyInstance方法创建一个动态代理类实例,这个方法需要传入三个参数,第一个参数是类加载器,用于加载这个代理类。第二个参数是Class数组,里面存放的是待实现的接口信息。第三个参数是InvocationHandler实例。

第10行调用代理类的helloWorld方法,运行结果:

代码语言:javascript
复制
public abstract void com.tuniu.distribute.openapi.common.annotation.MyIntf.helloWorld()

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

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

代码语言:javascript
复制
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个静态方法,分别为:

代码语言:javascript
复制
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实例。如果没有,则动态创建并返回。

代码语言:javascript
复制
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实例。

代码语言:javascript
复制
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类的静态变量

代码语言:javascript
复制
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方法源码。

代码语言:javascript
复制
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方法,看下是怎么生成二级键的。

代码语言:javascript
复制
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方法的源码。

代码语言:javascript
复制
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方法。

代码语言:javascript
复制
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方法就容易多了,它就是使用反射机制调用动态代理类的构造函数生成一个代理类实例的过程。

代码语言:javascript
复制
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

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

代码语言:javascript
复制
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实例。

代码语言:javascript
复制
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;
}

Cglib动态代理

和JDK动态代理不同,Cglib动态代理是基于Java的继承机制实现的。我们先来看一下Cglib动态代理的UML图。

从上图中可以看出,Cglib生成的动态代理类ProxyObject继承于目标类TargetSubject,并且持有一个类型为MethodInterceptor接口的实例引用,这个引用需要我们自己实现,这一点和JDK动态代理的InvocationHandler接口是一样的。我们还是以文章开头的例子来看,用Cglib是如何实现的。

CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。简单的实现举例:

代码语言:javascript
复制
//这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理
public class SayHello {
 public void say(){
  System.out.println("hello everyone");
 }
}

该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。

代码语言:javascript
复制
public class CglibProxy implements MethodInterceptor{
 private Enhancer enhancer = new Enhancer();
 public Object getProxy(Class clazz){
  //设置需要创建子类的类
  enhancer.setSuperclass(clazz);
  enhancer.setCallback(this);
  //通过字节码技术动态创建子类实例
  return enhancer.create();
 }
 //实现MethodInterceptor接口方法
 public Object intercept(Object obj, Method method, Object[] args,
   MethodProxy proxy) throws Throwable {
  System.out.println("前置代理");
  //通过代理类调用父类中的方法
  Object result = proxy.invokeSuper(obj, args);
  System.out.println("后置代理");
  return result;
 }
}

具体实现类:

代码语言:javascript
复制
public class DoCGLib {
 public static void main(String[] args) {
  CglibProxy proxy = new CglibProxy();
  //通过生成子类的方式创建代理类
  SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);
  proxyImp.say();
 }
}

输出结果:

代码语言:javascript
复制
前置代理
hello everyone
后置代理

Cglib动态代理的源码分析

可以看到Cglib在运行时生成的代理类是继承于我们自己的目标类ProxyText,从而实现代理的功能的。那么Cglib的代码到底是怎么生成的,我们继续往下看:

代码语言:javascript
复制
ProxyTest proxyTest = (ProxyTest) enhancer.create();

这段代码是我们调用Cglib生成代理类的代码,我们跟进去看一下,具体是如何实现的。

代码语言:javascript
复制
public class Enhancer extends AbstractClassGenerator {
   
   //创建代理类
   public Object create() {
      this.classOnly = false;
      this.argumentTypes = null;
      return this.createHelper();
    }
    //帮助类
    private Object createHelper() {
      this.preValidate();
      Object key = KEY_FACTORY.newInstance(this.superclass != null ? this.superclass.getName() : null, ReflectUtils.getNames(this.interfaces), this.filter == ALL_ZERO ? null : new WeakCacheKey(this.filter), this.callbackTypes, this.useFactory, this.interceptDuringConstruction, this.serialVersionUID);
      this.currentKey = key;
      //调用父类的create接口
      Object result = super.create(key);
      return result;
    }
}

我们看到具体的实现是调用父类的create接口实现,继续跟进去看看。

代码语言:javascript
复制
public abstract class AbstractClassGenerator<T> implements ClassGenerator {
    /**
    * 创建动态代理对象
    */
     protected Object create(Object key) {
        try {
            ClassLoader loader = this.getClassLoader();
            Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> cache = CACHE;
            AbstractClassGenerator.ClassLoaderData data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
            if (data == null) {
                Class var5 = AbstractClassGenerator.class;
                synchronized(AbstractClassGenerator.class) {
                    cache = CACHE;
                    data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
                    if (data == null) {
                        Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> newCache = new WeakHashMap(cache);
                        data = new AbstractClassGenerator.ClassLoaderData(loader);
                        newCache.put(loader, data);
                        CACHE = newCache;
                    }
                }
            }

            this.key = key;
            //此处是关键,跟进去会发现最终调用的是下面的generate方法产生动态代理的class对象
            Object obj = data.get(this, this.getUseCache());
            //通过反射生成具体的代理对象
            return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj);
        } catch (RuntimeException var9) {
            throw var9;
        } catch (Error var10) {
            throw var10;
        } catch (Exception var11) {
            throw new CodeGenerationException(var11);
        }
    }
    /**
    * 该方法式最终产生动态代理class的地方
    */
    protected Class generate(AbstractClassGenerator.ClassLoaderData data) {
        Object save = CURRENT.get();
        CURRENT.set(this);

        try {
            ClassLoader classLoader = data.getClassLoader();
            if (classLoader == null) {
                throw new IllegalStateException("ClassLoader is null while trying to define class " + this.getClassName() + ". It seems that the loader has been expired from a weak reference somehow. " + "Please file an issue at cglib's issue tracker.");
            } else {
                String className;
                synchronized(classLoader) {
                    className = this.generateClassName(data.getUniqueNamePredicate());
                    data.reserveName(className);
                    this.setClassName(className);
                }
                Class gen;
                if (this.attemptLoad) {
                    try {
                        gen = classLoader.loadClass(this.getClassName());
                        Class var25 = gen;
                        return var25;
                    } catch (ClassNotFoundException var20) {
                        ;
                    }
                }
                //此处是关键,调用DefaultGeneratorStrategy的generate方法生成class文件的二进制流,具体是通过asm生成的,感兴趣的同学可以跟进去再看看
                byte[] b = this.strategy.generate(this);
                className = ClassNameReader.getClassName(new ClassReader(b));
                ProtectionDomain protectionDomain = this.getProtectionDomain();
                //以下代码通过代理类class文件的二进制流生成具体的class对象
                synchronized(classLoader) {
                    if (protectionDomain == null) {
                        gen = ReflectUtils.defineClass(className, b, classLoader);
                    } else {
                        gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain);
                    }
                }

                Class var8 = gen;
                return var8;
            }
        } catch (RuntimeException var21) {
            throw var21;
        } catch (Error var22) {
            throw var22;
        } catch (Exception var23) {
            throw new CodeGenerationException(var23);
        } finally {
            CURRENT.set(save);
        }
    }
}

总结

CgLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CgLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CgLib合适,反之,使用JDK方式要更为合适一些。同时,由于CgLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。

JDK动态代理是在运行时JDK根据class文件的格式动态拼装class文件,并加载到jvm中生成代理对象的。而Cglib动态代理是通过ASM库来操作class文件动态生成代理类的。同时你应该了解到:JDK动态代理是基于java中的接口实现的,Cglib是基于java中的继承实现的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农沉思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档