cglib源码分析(一): 缓存和KEY(转载)

原文出处:http://www.cnblogs.com/cruze/p/3843996.html

 cglib是一个java 字节码的生成工具,它是对asm的进一步封装,提供了一系列class generator。研究cglib主要是因为它也提供了动态代理功能,这点和jdk的动态代理类似。

一、 Cache的创建

    与jdk动态代理一样,cglib也提供了缓存来提高系统的性能,对于已经生成的类,直接使用而不必重复生成。这里不得不提到一个比较重要的抽象类AbstractClassGenerator,它采用了模版方法的设计模式,protected Object create(Object key) 就是模版方法,它定义了类生成的过程。AbstractClassGenerator只有一个构造函数protected AbstractClassGenerator(Source source),入参是一个Source类型的对象,Source是AbstractClassGenerator里面的一个静态内部类,Source有两个字段 name用来记录class generator,cache 就是缓存,它和jdk动态代理一样都是用了WeakHashMap,并且类型也是<ClassLoader,<Object,Class>>:

复制代码
    protected static class Source {
        String name; //class generator的name,eg:如果使用Enhancer来生成增强类,name的值就为 net.sf.cglib.proxy. Enhancer
        Map cache = new WeakHashMap();
        public Source(String name) {
            this.name = name;
        }
}
复制代码

        每个class generator都必须继承AbstractClassGenerator并且实现 public void generateClass(ClassVisitor v) 方法用来生成所需要的类。每个class generator都有独立的缓存,比如 Enhancer 类中 private static final Source SOURCE = new Source(Enhancer.class.getName()); 在BeanGenerator 中 private static final Source SOURCE = new Source(BeanGenerator.class.getName()); 

二、 Cache的使用

    缓存的使用主要封装在AbstractClassGenerator的模版方法create中,下面是create方法的源码:

复制代码
synchronized (source) {
                ClassLoader loader = getClassLoader();
                Map cache2 = null;
                cache2 = (Map)source.cache.get(loader);
                if (cache2 == null) {
                    cache2 = new HashMap();
                    cache2.put(NAME_KEY, new HashSet());
                    source.cache.put(loader, cache2);
                } else if (useCache) {
                    Reference ref = (Reference)cache2.get(key);
                    gen = (Class) (( ref == null ) ? null : ref.get()); 
                }
…… 忽略若干代码
                if (useCache) {
                     cache2.put(key, new WeakReference(gen));
            }
}
复制代码

    生成类的缓存是按照ClassLoader来划分的,这是因为类的区分不仅根据类名还根据装载类的ClassLoader,也就是说同一个类被不同的ClassLoader加载,那么它们也是不同的,关于这部分内容可参考 http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3 。每个ClassLoader的缓存中都会有一个NAME_KEY 这个主要是用来对生成的class name进行去重,NAME_KEY 会在生成类命名策略里有进一步的说明。此处还使用useCache 来标记是否使用缓存,这给了用户比较灵活的选择。

三、Key的例子

    每个生成类在缓存中都会有一个key与之相对应。对于那些只与单个类相关的生成类,可以采用类名作为key。在动态代理中生成类不仅与目标类相关,还与使用的拦截类(MethodInterceptor),过滤类(CallbackFilter)相关,这样的话就要使用multi-vaules key来标识这个生成类,在cglib中multi-vaules 也是动态生成的,KeyFactory  就是生成multi-vaules的工厂类,它是一个抽象类,也就是说它不能被实例化,但是它提供了一系列的静态工厂方法来生成multi-vaules的工厂类,这里很拗口,下面是cglib源码包中的一个例子:

复制代码
package samples;
import net.sf.cglib.core.KeyFactory;
public class KeySample {
    private interface MyFactory {
        public Object newInstance(int a, char[] b, String d);
    }
    public static void main(String[] args) {
        MyFactory f = (MyFactory)KeyFactory.create(MyFactory.class);
        Object key1 = f.newInstance(20, new char[]{ 'a', 'b' }, "hello");
        Object key2 = f.newInstance(20, new char[]{ 'a', 'b' }, "hello");
        Object key3 = f.newInstance(20, new char[]{ 'a', '_' }, "hello");
        System.out.println(key1.equals(key2));
        System.out.println(key1.toString());
        System.out.println(key2.equals(key3));
    }
}
复制代码

 

运行结果是:

true
20, {a, b}, hello
false

 

      为了生成multi-vaules 的工厂类,我们必须提供一个接口来描述multi-vaules的结构(上例中该接口为MyFactory),该接口有且只有一个方法newInstance,该方法的返回值必须为Object,该方法的入参可以是任意的对象,元数据类型 或者是任意维的数组 但是入参不能为空,如果为空就和默认的构造函数相冲突。 在分析KeyFactory之前,我们将上例生成的multi-vaules工厂类进行一下反编译(jd-gui 由于版本的问题无法反编译,因而此处使用javap):

 View Code

 

从反编译的结果我们可以看出,生成的工厂类为samples.KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e,它继承了net.sf.cglib.core.KeyFactory 类,实现了samples.KeySample$MyFactory接口,同时也实现了工厂方法newInstance,所以上例中key1,key2,key3都是通过该工厂方法产生了key的对象。samples.KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e 中有两个构造函数,一个是默认的构造函数(由于工厂方法newInstance 为非静态方法,所以需要使用默认构造函数来生成第一个对象),另外一个构造函数的入参和 newInstance 入参一样,newInstance使用有参构造函数来生成multi-values key 对象。samples.KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e 为newInstance的每一个入参都生成了一个相同类型的field用来存储key的value。除此之外还提供了三个方法:hashCode, equals, toString.

hashCode 和 equals 在HashMap 的get方法中用于和存储的key进行比较,所以这两个方法是比较重要的。

四、KeyFactory 源码分析

KeyFactory的源码还是比较多的,接下来只对其中的关键代码进行分析:

 View Code
 
 
posted @ 2018-07-25 20:34  青青子衿J  阅读(243)  评论(0)    收藏  举报