JDK动态代理-二级缓存的实现机制

前言

​ 代理模式为对象提供一种代理以控制对这个对象的访问,而Java动态代理就是代理模式的一种实现,其优势是实现无侵入式的代码扩展,也就是方法的增强,让我们可以在不用修改源码的情况下,增强一些方法,比如在方法的前后做一些日志记录等等。

测试代码
/**
 * @Description: 代理的对象必须实现接口
 */
interface Subject{
    void rent();
}

/**
 * @Description: 需要代理的对象,有一个出租房屋的方法
 */
public class RealSubject implements Subject {
    @Override
    public void rent() {
        System.out.println("I want to rent my house");
    }
}

/**
 * @Description: 代理处理类,实现InvocationHandler接口,invoke为增强方法
 */
public class DynamicProxy implements InvocationHandler {

    private Object subject; // 这个就是要代理的真实对象
    
    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        
        System.out.println("before rent house"); // 前置记录

        System.out.println("Method: " + method); // 记录下执行的方法

        method.invoke(subject, args); // 执行原有的方法

        System.out.println("after rent house"); // 后置记录

        return null;
    }
}

/**
 * @Description: 测试类
 */
public class JDKProxyTest {
    public static void main(String[] args) {
        
        Subject realSubject = new RealSubject(); // 需代理的真实对象

        InvocationHandler handler = new DynamicProxy(realSubject); // 传进处理类

        /*
         * 通过Proxy的newProxyInstance方法来创建代理对象,需传入三个参数:
         * 第一个参数 handler.getClass().getClassLoader(),获取处理类的类加载器;
         * 第二个参数 realSubject.getClass().getInterfaces(),获取需代理对象的接口;
         * 第三个参数 handler,处理类;
         */
        Subject subject =                                       (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), 
                                realSubject.getClass().getInterfaces(), 
                                handler);

        System.out.println(subject.getClass().getSimpleName()); // L1
        subject.rent();
    }
}

执行结果:
$Proxy0  // L1处打印的代理对象的类名
before rent house // 前置记录
Method: public abstract void com.demo.srpingboot.common.JDKproxy.Subject.rent()
I want to rent my house // rent方法执行
after rent house // 后置记录
获取代理对象

代理对象在Proxy的newProxyInstance方法中获取,先着重理解getProxyClass0()方法,该方法生成代理类。

public class Proxy implements java.io.Serializable {
    
    /** 缓存机制,传入KeyFactory和ProxyClassFactory */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
    throws IllegalArgumentException
    {
        Objects.requireNonNull(h); // 非空

        final Class<?>[] intfs = interfaces.clone(); // 先clone被代理对象的接口
        ...

        /*
         * 查找或生成指定的代理类
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        try {
            ...
            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}); // L1 反射获取代理对象
        } catch (...) {
            ...  // 异常处理
        } 
    }   

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) { // 被代理的对象实现的接口不能超过65535
            throw new IllegalArgumentException("interface limit exceeded");
        }

        return proxyClassCache.get(loader, interfaces); // 从缓存中获取代理对象
    }
}

关于代码中会涉及到的两个函数式接口。

​ jdk底层使用了缓存以减少重复代理类的构建(二级缓存)。

  1. 首先看一下proxyClassCache,作为Proxy类私有静态域,在Proxy类被加载的时候生成一个全局唯一的WeakCache对象,并传入KeyFactory和ProxyClassFactory类型的对象,由这两个工厂生成二级缓存的key和对应的value。
  2. 在从缓存中取值之前,下列代码L1处先清除过期缓存,从清除过期缓存的过程中可以猜测到一些二级缓存的实现机制,包括一级缓存的key是一个CacheKey类型的对象,且这个对象不是一个”真正的对象“,而是一个弱引用对象,指向某个对象(而我们在L2处CacheKey的valueOf方法中得知,这个指向的对象是loader,也就是类加载器对象),另外WeakCache的reverseMap属性的key为二级缓存的value等。
  3. L2处将类加载器对象包装成弱引用对象,传入key为null时,将一个单例的Object对象作为一级缓存的key。 到这里查找缓存map的一级索引已经有了。
  4. L3处根据传入类加载器对象和接口数组,通过subKeyFactory(也就是传入KeyFactory对象)构建二级缓存的key。接着用这个key通过轮询和CAS的方式去获取或者生成二级缓存的value。
  5. 值得注意的是在代码L4处,创建了Factory这个工厂,这个工厂的生成二级缓存value最外层的工厂。并且在L5处直接将其作为二级缓存的value放入map中。也就是说此时二级缓存map的value值有可能是Factory对象,也有可能是CacheValue对象。
final class WeakCache<K, P, V> {
    /** 引用队列 */
    private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
    /** 缓存的底层实现, key为一级缓存, value为二级缓存。这里为了支持null, map的key类型设置为Object,这里支持null的意思是当没有传入类加载器时,默认将创建好的Object对象做为key,这一点下面会讲到 */
    private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
        = new ConcurrentHashMap<>();
    /** reverseMap记录了所有代理类生成器是否可用, 这是为了实现缓存的过期机制 */
    private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
        = new ConcurrentHashMap<>();
    /** 生成二级缓存key的工厂, 这里指的是KeyFactory类型 */
    private final BiFunction<K, P, ?> subKeyFactory;
    /** 生成二级缓存value的工厂, 这里指的是ProxyClassFactory类型 */
    private final BiFunction<K, P, V> valueFactory;
    
    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) { 
        // 构造方法,将两个传入的工厂作为私有属性
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }
    
    public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);
        expungeStaleEntries(); // L1,清除过期缓存

        Object cacheKey = CacheKey.valueOf(key, refQueue); //L2,构建弱引用对象

        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); // 获取二级缓存
        if (valuesMap == null) {
            // 二级缓存为空,则以CAS的方式放入数据,如果已存在返回原有的二级缓存,否则将构建的弱引                 // 用对象作为key,并new一个ConcurrentHashMap作为value放入一级缓存中(保证线程安全)
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            // 如果二级缓存已存在,赋值给valuesMap
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }
        // L3
        // 根据需代理类实现的接口数组length生成二级缓存的key,类型为Key1、Key2、Object、KeyX
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey); // 根据生成的key来获取二级缓存的value
        Factory factory = null; 

        while (true) { // 提供轮询机制
            if (supplier != null) {
                V value = supplier.get(); 
                if (value != null) {
                    return value;
                }
            }
            
            // 有两种情况supplier为空,一是缓存里压根没有,二是本身就为空(造成的原因可能是
            // 二级缓存的value对象被清除了或者工厂还没有成功install对象)
            
            // 懒加载Factory
            if (factory == null) { // L4
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                // 到这里表明subKey没有对应的值, 就将factory作为subKey对应的值放入
                supplier = valuesMap.putIfAbsent(subKey, factory); // L5
                if (supplier == null) {
                    supplier = factory;
                }
            } else {
                // 到这里表明其他线程抢先给supplier赋值了,则将原先的值覆盖
                if (valuesMap.replace(subKey, supplier, factory)) {
                    supplier = factory;
                } else {
                    // 覆盖失败则返回原先的值
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }
}
  1. 接着来看一下这个Factory,值得注意的是构建Factory时传入了四个参数,其中也包括二级缓存map和二级缓存的key,传入这个二级缓存map的原因是当然是为了生成真正的二级缓存value,CacheValue对象并替换掉原来的Factory对象。这里的双重校验,是解决延迟初始化的竞态条件"检查-初始化"。第一次生成代理类的时候存在一个延迟初始化的竞态条件。这里为了保证线程安全,第一次生成代理类时需要线程同步以保证线程安全,后续获取代理类则不需要,以减轻并发压力,因此引入了生成二级缓存时引入了Factory类。
private final class Factory对象 implements Supplier<V> {

    private final K key;
    private final P parameter;
    private final Object subKey;
    private final ConcurrentMap<Object, Supplier<V>> valuesMap;

    Factory(K key, P parameter, Object subKey,
            ConcurrentMap<Object, Supplier<V>> valuesMap) {
        this.key = key;
        this.parameter = parameter;
        this.subKey = subKey;
        this.valuesMap = valuesMap;
    }

    @Override
    public synchronized V get() {
        Supplier<V> supplier = valuesMap.get(subKey); // 重新检查
        if (supplier != this) {
            // 这里判断了一下获取到的Supplier对象是否为Factory类型,两种情况会不是Factory类型,
            // 第一种是在等待时,对应的二级缓存的value已经被替换为CacheValue对象,
            // 第二种是由于生成代理类失败被从二级缓存中移除了。
            return null;
        }
        // 还是Factory对象

        V value = null;
        try {
            // 委托valueFactory去生成代理类
            value = Objects.requireNonNull(valueFactory.apply(key, parameter));
        } finally {
            if (value == null) { //如果生成代理类失败, 就将这个二级缓存删除
                valuesMap.remove(subKey, this);
            }
        }
        assert value != null;

        // 将代理类包装成一个CacheValue对象,且这个对象也是个弱引用
        CacheValue<V> cacheValue = new CacheValue<>(value);

        // 将cacheValue放入reverseMap, 并对它进行标记
        reverseMap.put(cacheValue, Boolean.TRUE);

        // 将包装后的cacheValue放入二级缓存中, 这个操作必须成功, 否则就报错
        if (!valuesMap.replace(subKey, this, cacheValue)) {
            throw new AssertionError("Should not reach here");
        }

        // 顺利到这就返回由代理类包装成的CacheValue弱引用对象
        return value;
    }
}
  1. 最后来看一下这个ProxyClassFactory对象(valueFactory)如何生成代理类,也是通过类加载器对象和接口数组来生成。至此代理类已经被创建或者被找到,那么在Proxy代码L1处最后通过反射将代理对象创建即可。
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
    // 代理类名称前缀
    private static final String proxyClassNamePrefix = "$Proxy";
    // 代理类标记
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    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) {
            }
                ... // 异常处理
        }
            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) {
                // 如果全是公共的则使用com.sun.proxy包名
                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());
            }
        }
    }
清除过期缓存
/**
 * 清除过期缓存
 */
private void expungeStaleEntries() {
    CacheKey<K> cacheKey;
    /*
     * 循环引用队列,下面介绍弱引用时提到弱引用指向的对象被回收时,会放进传入的引用队列
     */
    while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
        /* 直接移除,因为在引用队列中的引用对象表示了该引用指向的被引用对象已经GC回收,这里注意
           被引用对象指向的对象和引用对象的区别 */
        cacheKey.expungeFrom(map, reverseMap);
    }
}

/**
 * CacheKy是WeakReference弱引用的子类,也是WeakCache的私有静态内部类,是一级缓存的key,
 * 当然key是Object类型的,是为了兼容null。
 */ 
private static final class CacheKey<K> extends WeakReference<K> {
    
    private static final Object NULL_KEY = new Object();
    
    private CacheKey(K key, ReferenceQueue<K> refQueue) { // 传入WeakCache的属性,引用队列
        super(key, refQueue); // 构造弱引用
        this.hash = System.identityHashCode(key);
    }

    static <K> Object valueOf(K key, ReferenceQueue<K> refQueue) {
        return key == null
            // key为null时,不能被弱引用,所以这里用了一个Object类型的单例,这里也解释了一级缓存的key             // 为Object类型的原因
            ? NULL_KEY 
            // 如果key不为null时,包装成一个弱引用对象
            : new CacheKey<>(key, refQueue);
    }
    void expungeFrom(ConcurrentMap<?, ? extends ConcurrentMap<?, ?>> map,
                     ConcurrentMap<?, Boolean> reverseMap) { // 传入缓存map和reverseMap
        ConcurrentMap<?, ?> valuesMap = map.remove(this);  // 直接移除
        if (valuesMap != null) { // valuesMap:二级缓存
            for (Object cacheValue : valuesMap.values()) { // 遍历二级缓存的value
                // 从这里也可以看出reverseMap的key为二级缓存的value
                reverseMap.remove(cacheValue); 
            }
        }
    }
}

/**
 * 弱引用,当一个堆中的对象仅仅被弱引用指向时(没有强引用指向),如果这时候JVM执行GC,则无论
 * 内存空间是否足够,这个对象都会被回收。
 *
 * Java四种引用类型(取自深入理解Java虚拟机)
 *    强引用:类似“Subject realSubject = new RealSubject();”,只要强引用存在,GC就不会
 *           回收对象;
 *    软引用:用来描述一些还有用并非必需的对象,对于软引用关联着的对象,在系统将要发生内存溢
 *           出异常之前将会把这些对象列回可回收范围之中进行第二次回收;
 *    弱引用:强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次GC;
 *    虚引用:也被称为幽灵引用或幻影引用,最弱的一种引用关系。一个对象是否有虚引用存在,完全
 *           不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。关联虚引用的唯一目
 *           的就是在一个对象被GC回收时收到一个系统通知。
 */
public class WeakReference<T> extends Reference<T> {

    public WeakReference(T referent) {
        super(referent);
    }
    
    /**
     * 被弱引用指向的对象(referent)在被回收后,会把弱引用对象(this),
     * 也就是WeakReference对象或者其子类的对象,放入队列ReferenceQueue中
     *
     * @param referent 被弱引用指向的对象
     * @param q 引用队列
     */
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
二级缓存key生成

​ 通过KeyFactory(subKeyFactory)工厂生成二级缓存的key。key的生成方式比较简单,直接根据被代理类实现的接口数量来决定生成哪个Key。

private static final Object key0 = new Object();
   
private static final class KeyFactory
    implements BiFunction<ClassLoader, Class<?>[], Object>
{
    @Override
    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);
        }
    }
}
函数式接口

Supplier

​ 生成一个T类型的Supplier容器,每次调用get方法返回一个不同的T类型实例。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

​ 用法:

public static void main(String[] args) {
    // 创建Supplier容器,声明类型,此时并不会调用对象的构造方法,不会创建对象
    Supplier<RealSubject> sup= TestSupplier::new;
    //调用get()方法,此时会调用对象的构造方法
    sup.get();
    sup.get();
}
执行结果:
    com.demo.srpingboot.common.JDKproxy.RealSubject@3b95a09c
    com.demo.srpingboot.common.JDKproxy.RealSubject@6ae40994

BiFunction

​ 与Function不同,可以传入三个参数,前两个参数做运算,第三个参数将执行结果返回。

@FunctionalInterface
public interface BiFunction<T, U, R> {

    R apply(T t, U u);

    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

​ 用法:

public static int compute(int a, 
                          int b, 
                          BiFunction<Integer, Integer, Integer> biFunction) {
    return biFunction.apply(a, b);
}

public static void main(String[] args) {
    compute(2, 3, (v1, v2) -> v1 + v2); // 5
    compute(2, 3, (v1, v2) -> v1 - v2); // -1
    compute(2, 3, (v1, v2) -> v1 * v2); // 6
}
总结

​ JDK动态代理底层使用二级缓存存放代理类,以减少代理类的重复构建以及快速创建代理对象。将类加载器包装成一个CacheKey对象作为一级索引,根据接口数组的数量产生不同的二级索引对象,并把通过类加载器和接口数组创建的代理类包装成CacheValue,将其放入二级缓存map中。在创建包装代理类为CacheValue的过程中,使用了一个Factory实现代理类的延迟初始化,巧妙的通过双重校验机制避免了创建代理类时线程安全的问题,同时又保证了读取代理类时的并发性能。

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