死磕cglib系列之二 AbstractClassGenerator缓存解析

AbstractClassGenerator简介

通过上一篇文章对Enhancer类的使用,我们大致对cglib的proxy功能有了一个基本了解,本篇从Enhancer类的父类AbstractClassGenerator源码入手,试着剖析一下作者在写cglib框架时候可能的思想。仔细阅读源码就发现该类作为父类,被很多的类继承,比如Enhancer、BeanCopier内部类、KeyFactory内部类等。可以说所有cglib内部使用proxy的地方都需要继承这个类作为父类。

AbstractClassGenerator内部维护了两级缓存,抽象了很多操作,并且定义了一些抽象方法用于具体的Generator子类去实现,从而实现了高可用的、灵活多变的proxy子类。

ClassGenerator

package net.sf.cglib.core;
import org.objectweb.asm.ClassVisitor;

public interface ClassGenerator {
    void generateClass(ClassVisitor v) throws Exception;
}

从源码分析,我们看到AbstractClassGenerator实现了ClassGenerator接口。而接口只提出了一个方法,从字面意义上理解为生成Class,入参为asm ClassVisitor,虽然只有一行代码。但是这基本上就是作者的根本思想了。用asm生成一个Class类。

深入AbstractClassGenerator

 /*****
     * 创建一个被代理对象,
     * 该类被所有AbstractClassGenerator的子类Generator所调用,
     * @param key
     * @return
     */
    protected Object create(Object key) {
        try {
            //一级缓存loader作为缓存key
            ClassLoader loader = getClassLoader();

            //获取对应的一级缓存Value
            Map<ClassLoader, ClassLoaderData> cache = CACHE;
            ClassLoaderData data = cache.get(loader);

            //static volatile 只有一个实例,存在线程竞争
            if (data == null) {
                synchronized (AbstractClassGenerator.class) {
                    cache = CACHE;
                    data = cache.get(loader);
                    if (data == null) {
                        Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
                        data = new ClassLoaderData(loader);
                        newCache.put(loader, data);
                        CACHE = newCache;
                    }
                }
            }
            this.key = key;
            //通过ClassLoaderData获取实例,存在判定是否用到缓存的逻辑
            Object obj = data.get(this, getUseCache());
            if (obj instanceof Class) {
                return firstInstance((Class) obj);
            }
            return nextInstance(obj);
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Exception e) {
            throw new CodeGenerationException(e);
        }
    }

统一create入口

create方法为所有子类生成proxy的统一调用入口。如上,create方法,接收一个key,返回一个Object对象,该Object对象的返回取决于firstInstance或nextInstance的实现。如下,比如Enhancer的实现:

//提出概念,获取第一个proxy实例,如果map中获取的对象为Class对象
protected Object firstInstance(Class type) throws Exception {
    if (classOnly) {
        return type;
    } else {
        return createUsingReflection(type);
    }
}

一级缓存WeakHashMap

private static volatile Map<ClassLoader, ClassLoaderData> CACHE = new WeakHashMap<ClassLoader, ClassLoaderData>();
//获取对应的一级缓存Value
Map<ClassLoader, ClassLoaderData> cache = CACHE;
ClassLoaderData data = cache.get(loader);

if (data == null) {
    synchronized (AbstractClassGenerator.class) {
        cache = CACHE;
        data = cache.get(loader);
        if (data == null) {
            Map<ClassLoader, ClassLoaderData> newCache = new WeakHashMap<ClassLoader, ClassLoaderData>(cache);
            data = new ClassLoaderData(loader);
            newCache.put(loader, data);
            CACHE = newCache;
        }
    }
}
范型定义的理解

如上,通过WeakHashMap定义了一级缓存,因为同一个class类被不同的ClassLoader加载以后,它们依然不同,换句话说,只有ClassLoader和Class才可以确定该Class在jvm中的唯一性.这样也就解释了为什么用这样的范型来表示。

WeakHashMap与WeakReference

这里简单说一下WeakHashMap,该类除了将Entry继承WeakReference,在其他层面基本上与普通的HashMap没有什么区别,只不过在每次操作map的时候都会调用expungeStaleEntries()方法,将ReferenceQueue中已经被gc的对象,在链表的存储中删除而已。

这和WeakReference本身的构造有关系,因WeakReference本身就提供了这种机制,在声明WeakReference对象的同时提供了ReferenceQueue传入的构造,当WeakReference对象被gc的同时,被gc掉的对象也会在ReferenceQueue中保存一份。而WeakHashMap的Entry正是利用了这个机制。

建议参考blog:

http://www.cnblogs.com/xdouby/p/6793184.html

关于线程并发volatile

这里由于CACHE对象是static的。所以只有一个实例,我们看到锁内部的操作是对于CACHE变量不断的new一个WeakHashMap的实例,并且在锁最后一步赋值给CACHE,博主的理解是这恰巧用到了LoadStore屏障,保证了指令执行顺序,并且及时刷新到主存区域,以供下一个线程读到正确的值。

这里提出一个问题,希望看到的朋友可以解惑

虽然ClassLoader作为key传入了WeakHashMap中,但我们不确定传入的该loader key是否还有强引用指向呢?如果loader还有强引用指向的话,那该缓存是不是永远不会清理掉该key呢?换句话说,作为key的loader对象生命周期是什么样子的呢?

二级缓存的过渡ClassLoaderData

  • AbstractClassGenerator的create()方法
this.key = key;

//通过ClassLoaderData获取实例,存在判定是否用到缓存的逻辑
Object obj = data.get(this, getUseCache());
if (obj instanceof Class) {
    return firstInstance((Class) obj);
}
return nextInstance(obj);

我们继续上面的话题,此时我们由确定了ClassLoaderData对象,我们看到马上调用了data.get(this, getUseCache());

  • ClassLoaderData的构造
public ClassLoaderData(ClassLoader classLoader) {
    if (classLoader == null) {
        throw new IllegalArgumentException("classLoader == null is not yet supported");
    }
    this.classLoader = new WeakReference<ClassLoader>(classLoader);
    
    //LoadingCache中的loader
    Function<AbstractClassGenerator, Object> load =
            new Function<AbstractClassGenerator, Object>() {
                public Object apply(AbstractClassGenerator gen) {
                    Class klass = gen.generate(ClassLoaderData.this);
                    return gen.wrapCachedClass(klass);
                }
            };
    generatedClasses = new LoadingCache<AbstractClassGenerator, Object, Object>(GET_KEY, load);
}
  • ClassLoaderData的get方法
public Object get(AbstractClassGenerator gen, boolean useCache) {
    //判定是否使用缓存
    if (!useCache) {
        //不使用缓存
      return gen.generate(ClassLoaderData.this);
    } else {
        //使用缓存
        //enhancerkey net.sf.cglib.proxy.Enhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72
        Object cachedValue = generatedClasses.get(gen);
        return gen.unwrapCachedValue(cachedValue);
    }
}

一路深入,我们最终发现原来调用的还是LoadingCache中的get方法,并且传入的是AbstractClassGenerator当前实例对象,而此前所做的一些操作也不过是在为LoadingCache中的操作打基础罢了。此时LoadingCache才是才神秘的,我们继续往下。

二级缓存终解析LoadingCache

public interface Function<K, V> {
    V apply(K key);
}
package net.sf.cglib.core.internal;

import java.util.concurrent.*;


/*****
 * 缓存对象,被包装在ClassLoaderData中GeneratedClass
 * get 返回相关对象
 * @param   AbstractClassGenerator
 * @param   Object
 * @param    Object
 *
 * new LoadingCache(GET_KEY, load);
 *
 *
 * 真正的缓存对象的提取
 * Function  抽象一个行为,用于封装一些逻辑,将特定的K传入,返回特定的value
 */
public class LoadingCache<K, KK, V> {
    //真正的二级缓存,value为WeakReference类型
    protected final ConcurrentMap<KK, Object> map;
    //该Function的apply方法将决定task的get的返回。也就间接的决定了map内存入的值
    protected final Function<K, V> loader;

    //封装了返回相应cachekey的逻辑
    protected final Function<K, KK> keyMapper;

    public static final Function IDENTITY = new Function() {
        public Object apply(Object key) {
            return key;
        }
    };

    /****
     *
     * @param keyMapper 用于转换key apply GET_KEY的实现,返回一个key
     *        private static final Function GET_KEY = new Function() {
     *             public Object apply(AbstractClassGenerator gen) {
     *                 return gen.key;
     *             }
     *         };
     *
     * @param loader 用于转换存储的value     Function load =
     *                     new Function() {
     *                         public Object apply(AbstractClassGenerator gen) {
     *                             Class klass = gen.generate(ClassLoaderData.this);
     *                             return gen.wrapCachedClass(klass);
     *                         }
     *                     };
     */
    public LoadingCache(Function<K, KK> keyMapper, Function<K, V> loader) {
        this.keyMapper = keyMapper;
        this.loader = loader;
        this.map = new ConcurrentHashMap<KK, Object>();
    }

    @SuppressWarnings("unchecked")
    public static <K> Function<K, K> identity() {
        return IDENTITY;
    }

    public V get(K key) {
        final KK cacheKey = keyMapper.apply(key);
        Object v = map.get(cacheKey);
        if (v != null && !(v instanceof FutureTask)) {
            return (V) v;
        }


        return createEntry(key, cacheKey, v);
    }


    /******
     * 线程竞争,则只有一个线程会竞争成功,没有竞争成功的线程则使用get请求重新加载
     * 当多个线程竞争同一个futuretask对象时,只有一个get方法在运行。
     */
    /**
     * Loads entry to the cache.
     * If entry is missing, put {@link FutureTask} first so other competing thread might wait for the result.
     * @param key original key that would be used to load the instance
     * @param cacheKey key that would be used to store the entry in internal map
     * @param v null or {@link FutureTask}
     * @return newly created instance
     */
    protected V createEntry(final K key, KK cacheKey, Object v) {
        FutureTask<V> task;
        boolean creator = false;
        if (v != null) {
            // Another thread is already loading an instance
            task = (FutureTask<V>) v;
        } else {
            task = new FutureTask<V>(new Callable<V>() {
                public V call() throws Exception {
                    return loader.apply(key);
                }
            });
            //如果已经存在,则不会覆盖已有的值,直接返回已存在的值
            //如果不存在,则向map中添加该键值对,并返回null
            Object prevTask = map.putIfAbsent(cacheKey, task);
            if (prevTask == null) {
                // creator does the load
                creator = true;
                task.run();
            } else if (prevTask instanceof FutureTask) {
                task = (FutureTask<V>) prevTask;
            } else {
                return (V) prevTask;
            }
        }

        V result;
        try {
            result = task.get();
        } catch (InterruptedException e) {
            throw new IllegalStateException("Interrupted while loading cache item", e);
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw ((RuntimeException) cause);
            }
            throw new IllegalStateException("Unable to load cache item", cause);
        }
        if (creator) {
            map.put(cacheKey, result);
        }
        return result;
    }
}
二级缓存的本质

成员变量提供了两个Function的概念,它是一个单函数接口,其实它是对trueKey = Function.apply(key),以及trueValue = Function.apply(value)的一个封装。相当于对map的kv进行了一层拦截。这里博主将Enhancer类的调用放入了进去。我们看到存入value的时候我们还用到了gen.wrapCachedClass包装方法,转换过来也就是

protected T wrapCachedClass(Class klass) {
    return (T) new WeakReference(klass);
}

又是WeakReference,所以,二级缓存的本质是Map中的存储value转换成了一个弱引用,以此来实现了二级缓存

线程并发的FutureTask

这里值得一提的是,如果FutureTask的get方法没有执行完成,那么在并发情况下,只会有一个线程在执行,其他线程要让出cpu执行权,阻塞等待,直到get方法有结果返回,以下提供两篇文章url

FutureTask:https://www.cnblogs.com/maypattis/p/5827671.html

LoadingCache的转换解析:https://zhuanlan.zhihu.com/p/41947763

总结

以上就是cglib的proxy创建都需要AbstractClassGenerator的缓存解析,有任何问题欢迎拍砖交流,这里总结一下我们用到的类清单,以便以后写出来更好的demo,更深层次的理解

  • AbstractClassGenerator 封装了proxy创建过程中缓存,名称生成等操作
  • WeakHashMap 与其他hash存储基本一致,只不过Entry继承了WeakReference维护了Queue来动态同步它的存储,这里不局限于链表或者树结构
  • WeakReference 提供2种构造,当且仅当只有弱引用(无强引用)指向的时候,发生gc则会回收掉该对象
  • ClassLoaderData 封装了ClassLoader对应缓存对象。
  • LoadingCache 维护CurrentHashMap成员存储弱引用来实现2级缓存,基于FutureTask.get实现并发情况下的线程阻塞,以屏蔽Class字节码生成的耗时争抢
  • FutureTask 其阻塞调用awaitDone方法值得借鉴与学习

思想理解

  • 抽象Function用于key和value的处理拦截,实现却在LoadingCache之外的ClassLoaderData与AbstractClassGenerator实现,抽象了转换,结耦了实现
  • 抽象了firstInstance,nextInstance方法,用于子类实现缓存中不同场景的存储,例如Enhancer和BeanCopier就是不同的实现。

你可能感兴趣的:(cglib)