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):

public class samples.KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e extends net.sf.cglib.core.KeyFactory implements samples.KeySample$MyFactory{
public samples.KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e();
Code:
0: aload_0
1: invokespecial #11; //Method net/sf/cglib/core/KeyFactory."<init>":()V
4: return public java.lang.Object newInstance(int, char[], java.lang.String);
Code:
0: new #2; //class samples/KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e
3: dup
4: iload_1
5: aload_2
6: aload_3
7: invokespecial #16; //Method "<init>":(I[CLjava/lang/String;)V
10: areturn public samples.KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e(int, char[], java.lang.String);
Code:
0: aload_0
1: invokespecial #11; //Method net/sf/cglib/core/KeyFactory."<init>":()V
4: aload_0
5: dup
6: iload_1
7: putfield #20; //Field FIELD_0:I
10: dup
11: aload_2
12: putfield #24; //Field FIELD_1:[C
15: dup
16: aload_3
17: putfield #28; //Field FIELD_2:Ljava/lang/String;
20: return public int hashCode();
Code:
0: sipush 179
3: aload_0
4: getfield #20; //Field FIELD_0:I
7: swap
8: ldc #31; //int 467063
10: imul
11: swap
12: iadd
13: aload_0
14: getfield #24; //Field FIELD_1:[C
17: dup
18: ifnull 48
21: astore_1
22: iconst_0
23: istore_2
24: goto 39
27: aload_1
28: iload_2
29: caload
30: swap
31: ldc #31; //int 467063
33: imul
34: swap
35: iadd
36: iinc 2, 1
39: iload_2
40: aload_1
41: arraylength
42: if_icmplt 27
45: goto 49
48: pop
49: aload_0
50: getfield #28; //Field FIELD_2:Ljava/lang/String;
53: swap
54: ldc #31; //int 467063
56: imul
57: swap
58: dup
59: ifnull 68
62: invokevirtual #35; //Method java/lang/Object.hashCode:()I
65: goto 70
68: pop
69: iconst_0
70: iadd
71: ireturn public boolean equals(java.lang.Object);
Code:
0: aload_1
1: instanceof #2; //class samples/KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e
4: ifeq 133
7: aload_0
8: getfield #20; //Field FIELD_0:I
11: aload_1
12: checkcast #2; //class samples/KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e
15: getfield #20; //Field FIELD_0:I
18: if_icmpne 133
21: aload_0
22: getfield #24; //Field FIELD_1:[C
25: aload_1
26: checkcast #2; //class samples/KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e
29: getfield #24; //Field FIELD_1:[C
32: dup2
33: ifnonnull 43
36: ifnonnull 49
39: pop2
40: goto 93
43: ifnull 49
46: goto 53
49: pop2
50: goto 133
53: dup2
54: arraylength
55: swap
56: arraylength
57: if_icmpeq 64
60: pop2
61: goto 133
64: astore_2
65: astore_3
66: iconst_0
67: istore 4
69: goto 86
72: aload_2
73: iload 4
75: caload
76: aload_3
77: iload 4
79: caload
80: if_icmpne 133
83: iinc 4, 1
86: iload 4
88: aload_2
89: arraylength
90: if_icmplt 72
93: aload_0
94: getfield #28; //Field FIELD_2:Ljava/lang/String;
97: aload_1
98: checkcast #2; //class samples/KeySample$MyFactory$$KeyFactoryByCGLIB$$7116a61e
101: getfield #28; //Field FIELD_2:Ljava/lang/String;
104: dup2
105: ifnonnull 115
108: ifnonnull 121
111: pop2
112: goto 131
115: ifnull 121
118: goto 125
121: pop2
122: goto 133
125: invokevirtual #39; //Method java/lang/Object.equals:(Ljava/lang/Object;)Z
128: ifeq 133
131: iconst_1
132: ireturn
133: iconst_0
134: ireturn public java.lang.String toString();
Code:
0: new #43; //class java/lang/StringBuffer
3: dup
4: invokespecial #44; //Method java/lang/StringBuffer."<init>":()V
7: aload_0
8: getfield #20; //Field FIELD_0:I
11: invokevirtual #48; //Method java/lang/StringBuffer.append:(I)Ljava/lang/StringBuffer;
14: goto 23
17: pop
18: ldc #50; //String null
20: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
23: ldc #55; //String ,
25: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
28: aload_0
29: getfield #24; //Field FIELD_1:[C
32: dup
33: ifnull 96
36: swap
37: ldc #57; //String {
39: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
42: swap
43: astore_1
44: iconst_0
45: istore_2
46: goto 72
49: aload_1
50: iload_2
51: caload
52: invokevirtual #60; //Method java/lang/StringBuffer.append:(C)Ljava/lang/StringBuffer;
55: goto 64
58: pop
59: ldc #50; //String null
61: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
64: ldc #55; //String ,
66: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
69: iinc 2, 1
72: iload_2
73: aload_1
74: arraylength
75: if_icmplt 49
78: dup
79: dup
80: invokevirtual #63; //Method java/lang/StringBuffer.length:()I
83: iconst_2
84: isub
85: invokevirtual #67; //Method java/lang/StringBuffer.setLength:(I)V
88: ldc #69; //String }
90: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
93: goto 102
96: pop
97: ldc #50; //String null
99: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
102: ldc #55; //String ,
104: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
107: aload_0
108: getfield #28; //Field FIELD_2:Ljava/lang/String;
111: dup
112: ifnull 124
115: invokevirtual #71; //Method java/lang/Object.toString:()Ljava/lang/String;
118: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
121: goto 130
124: pop
125: ldc #50; //String null
127: invokevirtual #53; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
130: invokevirtual #72; //Method java/lang/StringBuffer.toString:()Ljava/lang/String;
133: areturn }

从反编译的结果我们可以看出,生成的工厂类为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的源码还是比较多的,接下来只对其中的关键代码进行分析:

        public void generateClass(ClassVisitor v) {
ClassEmitter ce = new ClassEmitter(v);
//对定义key工厂类结构的接口进行判断,判断该接口是否只有newInstance一个方法,newInstance的返回值是否为Object
Method newInstance = ReflectUtils.findNewInstance(keyInterface);
if (!newInstance.getReturnType().equals(Object.class)) {
throw new IllegalArgumentException("newInstance method must return Object");
} //获取newInstance的入参类型,此处使用ASM的Type来定义
Type[] parameterTypes = TypeUtils.getTypes(newInstance.getParameterTypes());
ce.begin_class(Constants.V1_2,
Constants.ACC_PUBLIC,
getClassName(),
KEY_FACTORY,
new Type[]{ Type.getType(keyInterface) },
Constants.SOURCE_FILE);
//生成默认构造函数
EmitUtils.null_constructor(ce); //生成newInstance 工厂方法
EmitUtils.factory_method(ce, ReflectUtils.getSignature(newInstance)); //生成有参构造方法
int seed = 0;
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC,
TypeUtils.parseConstructor(parameterTypes),
null);
e.load_this();
e.super_invoke_constructor();
e.load_this();
for (int i = 0; i < parameterTypes.length; i++) {
seed += parameterTypes[i].hashCode();
//为每一个入参生成一个相同类型的类字段
ce.declare_field(Constants.ACC_PRIVATE | Constants.ACC_FINAL,
getFieldName(i),
parameterTypes[i],
null);
e.dup();
e.load_arg(i);
e.putfield(getFieldName(i));
}
e.return_value();
e.end_method(); //生成hashCode
e = ce.begin_method(Constants.ACC_PUBLIC, HASH_CODE, null);
int hc = (constant != 0) ? constant : PRIMES[(int)(Math.abs(seed) % PRIMES.length)];
int hm = (multiplier != 0) ? multiplier : PRIMES[(int)(Math.abs(seed * 13) % PRIMES.length)];
e.push(hc);
for (int i = 0; i < parameterTypes.length; i++) {
e.load_this();
e.getfield(getFieldName(i));
EmitUtils.hash_code(e, parameterTypes[i], hm, customizer);
}
e.return_value();
e.end_method(); //生成equals函数,在equals函数中对每个入参都进行判断
e = ce.begin_method(Constants.ACC_PUBLIC, EQUALS, null);
Label fail = e.make_label();
e.load_arg(0);
e.instance_of_this();
e.if_jump(e.EQ, fail);
for (int i = 0; i < parameterTypes.length; i++) {
e.load_this();
e.getfield(getFieldName(i));
e.load_arg(0);
e.checkcast_this();
e.getfield(getFieldName(i));
EmitUtils.not_equals(e, parameterTypes[i], fail, customizer);
}
e.push(1);
e.return_value();
e.mark(fail);
e.push(0);
e.return_value();
e.end_method(); //生成toString方法
e = ce.begin_method(Constants.ACC_PUBLIC, TO_STRING, null);
e.new_instance(Constants.TYPE_STRING_BUFFER);
e.dup();
e.invoke_constructor(Constants.TYPE_STRING_BUFFER);
for (int i = 0; i < parameterTypes.length; i++) {
if (i > 0) {
e.push(", ");
e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
}
e.load_this();
e.getfield(getFieldName(i));
EmitUtils.append_string(e, parameterTypes[i], EmitUtils.DEFAULT_DELIMITERS, customizer);
}
e.invoke_virtual(Constants.TYPE_STRING_BUFFER, TO_STRING);
e.return_value();
e.end_method(); ce.end_class();
}

cglib源码分析(一): 缓存和KEY的更多相关文章

  1. jQuery1.9.1源码分析--数据缓存Data模块

    jQuery1.9.1源码分析--数据缓存Data模块 阅读目录 jQuery API中Data的基本使用方法介绍 jQuery.acceptData(elem)源码分析 jQuery.data(el ...

  2. lodash源码分析之缓存方式的选择

    每个人心里都有一团火,路过的人只看到烟. --<至爱梵高·星空之谜> 本文为读 lodash 源码的第八篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash gitb ...

  3. lodash源码分析之缓存使用方式的进一步封装

    在世界上所有的民族之中,支配着他们的喜怒选择的并不是天性,而是他们的观点. --卢梭<社会与契约论> 本文为读 lodash 源码的第九篇,后续文章会更新到这个仓库中,欢迎 star:po ...

  4. 【转】MaBatis学习---源码分析MyBatis缓存原理

    [原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...

  5. Cglib源码分析 invoke和invokeSuper的差别(转)

    原文 https://blog.csdn.net/makecontral/article/details/79593732 Cglib的实例 本文重在源码的分析,Cglib的使用不再复述. //被代理 ...

  6. cglib源码分析--转

    原文地址:http://www.iteye.com/topic/799827 背景 前段时间在工作中,包括一些代码阅读过程中,spring aop经常性的会看到cglib中的相关内容,包括BeanCo ...

  7. jQuery 2.0.3 源码分析 数据缓存

    历史背景: jQuery从1.2.3版本引入数据缓存系统,主要的原因就是早期的事件系统 Dean Edwards 的 ddEvent.js代码 带来的问题: 没有一个系统的缓存机制,它把事件的回调都放 ...

  8. cglib源码分析(四):cglib 动态代理原理分析

    本文分下面三个部分来分析cglib动态代理的原理. cglib 动态代理示例 代理类分析 Fastclass 机制分析 一.cglib 动态代理示例 public class Target{ publ ...

  9. cglib源码分析(二):Class name 生成策略

    一.如何获取动态生成的class 字节码 结合生成的class文件是一个学习cglib的比较好的方法.在cglib中,生成的class文件默认只存储在内存中,我们可以在代码中加入下面语句来获取clas ...

随机推荐

  1. python还不能作为主要编程语言的原因:

    1.不太熟悉,容易犯新手错误,2.调试方法不同3.写了一个函数,是否语法正确,不能知道,只有具体调用它的时候才知道4.编辑器太业余,没有输入联想功能5.要查找一个函数或变量在哪里定义的,只能通过搜索的 ...

  2. 【原创翻译】Reducing Branch Delay to Zero in Pipelined Processors

    在流水线处理器中减少分支延迟到零 Antonio M. Gonzalez and Jose M. Llaberia 摘要 一种减少流水处理器中分支延迟到零的机制将在本文被描述以及评估.这种机制基于多重 ...

  3. bzoj1497

    这道题让我涨姿势了 对于这类问题,我们称作最大权闭合图问题 就是每个点都有一个点权,要求选择一个点集,其中每个点的指向的点也在点集中,使这样一个点权和最大 对于这种问题,我们添加源点s,汇点t 对于点 ...

  4. POJ2104 区间第k小

    题意就是区间第k大…… 题解: 前段时间用主席树搞掉了…… 如今看到划分树,是在想来写一遍,结果18号对着学长的代码调了一上午连样例都没过,好桑心…… 今天在做NOI2010超级钢琴,忽然发现用划分树 ...

  5. PHP静态化之真静态化

    参考文献:http://blog.sina.com.cn/s/blog_66aa1142010114lc.html 采用了动态服务器技术生成静态HTML的做法,这样做的好处是:一是能减轻其服务器的负担 ...

  6. Oracle to_char格式化函数

    转:http://www.cnblogs.com/reborter/archive/2008/11/28/1343195.html Postgres 格式化函数提供一套有效的工具用于把各种数据类型(日 ...

  7. GPIO

    一.什么是GPIO?       首先应该理解什么是GPIO.GPIO,英文全称为General-Purpose IO ports,也就是通用IO口.在嵌入式系统中常常有数量众多,但是结构却比较简单的 ...

  8. arcgis android 加载google切片 天地图切片 并且能进行缓存

    废话不多说,直接下载,看layer包! https://github.com/Young-Ken/android-gis

  9. Unity 时间缩放状态下的特效播放

    时间缩放状态下,比如 Time.timeScale 缩小为 0 或者 0.000001 等极小值时,若想将特效的播放速度放大相同的倍数,即修改 ParticleSystem.playbackSpeed ...

  10. 【HTML】Advanced2:Conditional Comments

    1.try and figure out what is sensible for you to support. Are your web site visitors likely to be us ...