本文翻譯自:https://dzone.com/articles/cglib-missing-manual,如果覺得有幫助,請(qǐng)為作者點(diǎn)贊!
作為byte code庫,CGLib是許多著名的java框架(Hibernate、Spring等)比較流行的選擇。Byte code庫允許在java應(yīng)用的編譯階段之后,對(duì)class進(jìn)行操作或者動(dòng)態(tài)創(chuàng)建新的class。因?yàn)閖ava虛擬機(jī)是在需要class時(shí)才加載它,這就讓在程序運(yùn)行時(shí)加載新的(新創(chuàng)建或者修改)class成為可能。比如,Hibernate在生成動(dòng)態(tài)代理時(shí),就會(huì)使用的cglib庫生成。Hibernate在返回查詢數(shù)據(jù)時(shí),返回的其實(shí)是一個(gè)代理版本的對(duì)象,而不是一個(gè)包含數(shù)據(jù)庫中完整數(shù)據(jù)的對(duì)象,只有當(dāng)需要某個(gè)字段時(shí),才真正對(duì)其進(jìn)行加載。又比如,Spring在為方法添加安全性規(guī)則時(shí),使用的也是cglib庫。
Spring在訪問方法時(shí),會(huì)首先校驗(yàn)是否能訪問該方法,僅當(dāng)校驗(yàn)通過后才調(diào)用到具體的方法。而不是,直接就去訪問你需要的方法。另外一個(gè)典型的cglib應(yīng)用場(chǎng)景是在mock框架中,比如mockito。mock框架會(huì)使用cglib來代理目標(biāo)類,并將目標(biāo)類的所有方法都覆蓋成空方法(同時(shí)會(huì)添加一些跟蹤邏輯)。
與ASM庫(另外一個(gè)byte code庫,cglib基于ASM提供一些高級(jí)別的byte code操作功能)不同,cglib提供了更高級(jí)別的byte code操作,可以讓用戶在不了解任何Java類編譯原理的情況下使用。很不幸的是,cglib的文檔相當(dāng)?shù)亩?,甚至可以說相當(dāng)于沒有。除了一篇2005年寫的介紹Enhance類的博客之外,沒有更多的可以找到的資料了(譯者注:原文寫于2014年7月,可能當(dāng)時(shí)cglib的使用者較少),這篇博客將嘗試著對(duì)cglib以及其API進(jìn)行說明和事例演示。
Enhancer
讓我們從Enhancer類開始講解,該類可能是cglib庫中最常用的類了。一個(gè)Enhancer實(shí)例可以為一個(gè)不實(shí)現(xiàn)任何接口的類創(chuàng)建代理,可以將Enhancer與Java 1.3中引入的Proxy類進(jìn)行比較。Enhancer會(huì)動(dòng)態(tài)的為指定類創(chuàng)建子類,該子類的所有方法都可以被攔截處理。與Java 1.3中的Proxy不一樣,Enhancer不僅能實(shí)現(xiàn)接口的動(dòng)態(tài)代理,還可以實(shí)現(xiàn)類的動(dòng)態(tài)代理。
后續(xù)的實(shí)例會(huì)基于以下的POJO進(jìn)行演示:
public class SampleClass {
public String test(String input) {
return "Hello world!";
}
}
test(String)方法的返回值可以通過使用cglib的Enhancer和回調(diào)類FexedValue輕松替換掉:
@Test
public void testFixedValue() throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "Hello cglib!";
}
});
SampleClass proxy = (SampleClass) enhancer.create();
assertEquals("Hello cglib!", proxy.test(null));
}
在上面的事例中,enhancer將會(huì)返回一個(gè)增強(qiáng)的SampleClass的子類的實(shí)例,該實(shí)例會(huì)攔截所有方法的調(diào)用,從而直接返回callback中定義的返回值Hello cglib!。該增強(qiáng)實(shí)例是通過Enhancer#create(Object...)方法創(chuàng)建的(譯者注:在譯者使用的版本3.2.10中,該方法已經(jīng)不存在),該方法可輸入多個(gè)參數(shù),這些參數(shù)會(huì)被用于調(diào)用SampleClass的構(gòu)造方法(盡管構(gòu)造方法在字節(jié)碼級(jí)別僅僅是普通的java方法,但是Enhancer不會(huì)增強(qiáng)(代理)構(gòu)造方法。同時(shí)Enhancer也不能增強(qiáng)static或者final關(guān)鍵字定義的class)。如果你只需要?jiǎng)?chuàng)建一個(gè)增強(qiáng)class,而不需要?jiǎng)?chuàng)建其實(shí)例,你可以使用Enhancer#createClass方法,當(dāng)創(chuàng)建了增強(qiáng)class之后,你就可以使用它來動(dòng)態(tài)創(chuàng)建實(shí)例了。增強(qiáng)class將包含SampleClass的所有構(gòu)造方法,并且會(huì)將構(gòu)造方法的調(diào)用都代理到SampleClass。
需要注意的是,SampleClass的所有方法的調(diào)用都會(huì)被代理,包括從java.lang.Object繼承而來的方法。所以,調(diào)用proxy.toString()同樣會(huì)返回"Hello cglib!"。這就會(huì)導(dǎo)致調(diào)用proxy.hashCode()方法拋出ClassCastException異常,因?yàn)檎{(diào)用proxy.hashCode()會(huì)被代理并返回"Hello cglib!"這個(gè)字符串類型的值,但是Object#hashCode方法卻需要一個(gè)int類型的值。
通過觀察可以做出另外一個(gè)推斷:final方法是不會(huì)被代理(攔截)的,比如,Object#getClass會(huì)返回一個(gè)類似于"SampleClass$$EnhancerByCGLIB$$e277c63c"的類名。這個(gè)類名是cglib隨機(jī)生成的,目的是為了避免生成的類名沖突。cglib生成的類與被增強(qiáng)的類包名相同,這樣就可以實(shí)現(xiàn)覆蓋package-private方法了。與不能增強(qiáng)(代理)的final方法類似,通過生成子類實(shí)現(xiàn)增強(qiáng)的方案不能增強(qiáng)(代理)final類。所以,對(duì)于Hibernate這樣的框架,不能持久化final類。
下面,讓我們來使用更強(qiáng)大的callback——InvocationHandler:
@Test
public void testInvocationHandler() throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getDeclaringClass() != Object.class
&& method.getReturnType() == String.class) {
return "Hello cglib!";
} else {
throw new RuntimeException("Do not know what to do.");
}
}
});
SampleClass proxy = (SampleClass) enhancer.create();
assertEquals("Hello cglib!", proxy.test(null));
assertNotEquals("Hello cglib!", proxy.toString());
}
這個(gè)callback實(shí)現(xiàn),可以讓我們通過判斷調(diào)用的方法進(jìn)行處理并返回?cái)?shù)據(jù)。需要注意的是,當(dāng)使用InvocationHandler#invoke方法的輸入?yún)?shù)proxy進(jìn)行方法調(diào)用的時(shí)候,會(huì)導(dǎo)致無限循環(huán)。因?yàn)橥ㄟ^proxy調(diào)用的任何方法,都會(huì)被同一個(gè)InvocationHandler代理,從而調(diào)用其invoke方法,導(dǎo)致無限循環(huán)。為了避免這種情況,可以使用MethodInterceptor:
@Test
public void testMethodInterceptor() throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
throws Throwable {
if(method.getDeclaringClass() != Object.class
&& method.getReturnType() == String.class) {
return "Hello cglib!";
} else {
proxy.invokeSuper(obj, args);
}
}
});
SampleClass proxy = (SampleClass) enhancer.create();
assertEquals("Hello cglib!", proxy.test(null));
assertNotEquals("Hello cglib!", proxy.toString());
proxy.hashCode();// Does not throw an exception or result in an endless loop.
}
MethodInterceptor可以讓我們對(duì)被攔截的方法進(jìn)行完全控制,并提供了一些工具用于我們調(diào)用被增強(qiáng)類SampleClass的原始方法(而不是被代理過后的方法)。但是,為什么需要使用原始方法呢?因?yàn)橹苯邮褂迷挤椒ǖ男矢?,而cglib框架一般會(huì)被用于一些非常注重效率的框架中。MethodInterceptor的創(chuàng)建和鏈入需要生成額外的字節(jié)碼(類)(byte code),同時(shí)會(huì)創(chuàng)建一些InvocationHandler不會(huì)創(chuàng)建的運(yùn)行時(shí)對(duì)象(與InvocationHandler對(duì)比,性能更差)。由于此種原因,cglib還提供了另外的一些callback實(shí)現(xiàn):
- LazyLoader:盡管LazyLoader僅有的一個(gè)方法與FixedValue的方法一樣,但是LazyLoader與FixedValue從根本上還是不一樣的。LazyLoader假設(shè)其返回的是增強(qiáng)子類的實(shí)例,這個(gè)實(shí)例僅在第一次訪問其方法時(shí)才返回,然后調(diào)用者會(huì)緩存該實(shí)例并用于后續(xù)調(diào)用,這個(gè)特性可以用于對(duì)象的創(chuàng)建需要耗費(fèi)大量資源的場(chǎng)景。需要注意的是,不管是proxy對(duì)象(FixedValue)還是lazy load對(duì)象(LazyLoader),都只能使用被增強(qiáng)類的構(gòu)造方法來創(chuàng)建對(duì)象。因此,請(qǐng)確保被增強(qiáng)類擁有一個(gè)不耗時(shí)的構(gòu)造方法,或者被增強(qiáng)類實(shí)現(xiàn)了接口,你可以通過使用Enhancer#create(Object...)方法來選擇使用的構(gòu)造方法。
- Dispatcher:與LazyLoader的功能相似,但是Dispatcher會(huì)在每個(gè)方法調(diào)用時(shí)都創(chuàng)建實(shí)例。這個(gè)特性適用于:在不改變引用對(duì)象的情況下,切換其實(shí)現(xiàn)類。同樣需要注意的是,對(duì)象的創(chuàng)建只會(huì)使用到被增強(qiáng)類的構(gòu)造方法。
- ProxyRefDispatcher:這個(gè)callback的方法需要一個(gè)輸入?yún)?shù),該輸入?yún)?shù)為增強(qiáng)后的類的實(shí)例。這就允許將一個(gè)方法的調(diào)用代理到另外一個(gè)方法上去,這種使用方式很容易導(dǎo)致無限循環(huán),特別是當(dāng)在ProxyRefDispatcher#loadObject(Object)方法中始終調(diào)用同一個(gè)方式時(shí)必然會(huì)導(dǎo)致無限循環(huán)。(譯者注:其實(shí)ProxyRefDispatcher與Dispatcher類似,只不過多個(gè)一個(gè)輸入?yún)?shù)而已,該輸入?yún)?shù)就是proxy。)
- NoOp:不像該類的名字所暗示的那樣(什么都不做),該callback直接將方法的調(diào)用代理到原始類,不會(huì)增加任何額外特性。
現(xiàn)在看來,最后兩個(gè)callback可能不會(huì)引起你的注意。什么場(chǎng)景下才會(huì)出現(xiàn)將一個(gè)類增強(qiáng)之后,還需要使用沒增強(qiáng)的方法呢?你是對(duì)的,這兩個(gè)callback一般只會(huì)與CallbackFilter結(jié)合起來使用,下面是事例:
@Test
public void testCallbackFilter() throws Exception {
Enhancer enhancer = new Enhancer();
CallbackHelper callbackHelper = new CallbackHelper(SampleClass.class, new Class[0]) {
@Override
protected Object getCallback(Method method) {
if(method.getDeclaringClass() != Object.class
&& method.getReturnType() == String.class) {
return new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "Hello cglib!";
};
}
} else {
return NoOp.INSTANCE; // A singleton provided by NoOp.
}
}
};
enhancer.setSuperclass(MyClass.class);
enhancer.setCallbackFilter(callbackHelper);
enhancer.setCallbacks(callbackHelper.getCallbacks());
SampleClass proxy = (SampleClass) enhancer.create();
assertEquals("Hello cglib!", proxy.test(null));
assertNotEquals("Hello cglib!", proxy.toString());
proxy.hashCode(); // Does not throw an exception or result in an endless loop.
}
Enhancer的方法Enhancer#setCallbackFilter(CallbackFilter)接收一個(gè)CallbackFilter參數(shù),這個(gè)方法期望被增強(qiáng)類的方法調(diào)用都被映射到一個(gè)Callback實(shí)例集合中去(譯者注:該集合中能包含處理被增強(qiáng)類所有方法的Callback)。當(dāng)調(diào)用proxy的方法時(shí),Enhancer會(huì)根據(jù)CallbackFilter的算法選擇出合適的Callback,然后將強(qiáng)求轉(zhuǎn)發(fā)到Callback。為了讓CallbackFilter的創(chuàng)建不那么費(fèi)勁,cglib提供了一個(gè)幫助類CallbackHelper。該類實(shí)現(xiàn)了CallbackFilter,且提供了可以直接為你創(chuàng)建Callback實(shí)例集合的方法。上面事例中的proxy功能等同于MethodInterceptor事例中的proxy,但是CallbackFilter允許你將編寫自定義Callback邏輯與分發(fā)邏輯分開編寫(解耦)。
How does it work?(這部分涉及到cglib的實(shí)現(xiàn)原理,翻譯難度較大,建議查看原文。)
當(dāng)Enhancer創(chuàng)建一個(gè)增強(qiáng)類時(shí),它會(huì)為每一個(gè)Callback創(chuàng)建一個(gè)private static變量,且該操作是在被代理類創(chuàng)建之后執(zhí)行的。這就意味著,cglib創(chuàng)建的類不能被復(fù)用,因?yàn)樽?cè)的callback不會(huì)成為類定義的一部分,而只是cglib在JVM加載類之后手動(dòng)添加的。同時(shí),從技術(shù)層面來說由cglib創(chuàng)建的類在初始化后是還未達(dá)到ready狀態(tài)的,比如該類不能通過網(wǎng)絡(luò)發(fā)送到另一臺(tái)機(jī)器,因?yàn)樵谀繕?biāo)機(jī)器上,該類可能并不存在(譯者注:目標(biāo)機(jī)器就算同樣算法生成了類,但是類名還是可能不一樣)。
對(duì)于不同的Callback類,cglib可能會(huì)注冊(cè)不同的額外變量。比如,MethodInterceptor就會(huì)注冊(cè)兩個(gè)private static變量(一個(gè)用于保存Method的反射,另一個(gè)是MethodProxy的反射)到代理的每個(gè)方法中。需要注意的是,MethodProxy會(huì)過渡使用FastClass,而FastClass的創(chuàng)建會(huì)觸發(fā)額外的類的創(chuàng)建,后面將會(huì)詳細(xì)介紹FastClass。
由于以上所有原因,請(qǐng)?jiān)谑褂肊nhancer的時(shí)候多多注意。在注冊(cè)callback的時(shí)候需要格外小心,比如MethodInterceptor會(huì)額外創(chuàng)建一些類,同時(shí)還會(huì)在增強(qiáng)類中注冊(cè)額外的static變量。這在將callback保存為靜態(tài)變量的時(shí),尤為危險(xiǎn):這可能會(huì)隱式的導(dǎo)致從不會(huì)對(duì)增強(qiáng)類進(jìn)行垃圾回收(除非是它的Classloader被回收)。另外一種危險(xiǎn)的情況是,使用匿名類時(shí),會(huì)使用到其外層類的引用?;叵胍幌律厦娴氖吕?/p>
@Test
public void testFixedValue() throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
return "Hello cglib!";
}
});
SampleClass proxy = (SampleClass) enhancer.create();
assertEquals("Hello cglib!", proxy.test(null));
}
這個(gè)FixedValue的匿名子類,將會(huì)變得很難從被增強(qiáng)類SampleClass中進(jìn)行引用(譯者注:不太明白這句的意思),因此這個(gè)匿名的子類以及包含這個(gè)@Test放到的類將永遠(yuǎn)不會(huì)被垃圾回收,這將會(huì)導(dǎo)致嚴(yán)重的內(nèi)存泄露。因此,不要在cglib中使用非靜態(tài)類(我在這篇博客中使用匿名類僅僅是為了讓事例更短小些)。
最后,千萬不要攔截Object#finalize()方法!由于cglib是通過子類方式實(shí)現(xiàn)的代理,所以finalize方法會(huì)被覆蓋,但是覆蓋finalize通常不是個(gè)好主意(譯者注:鏈接文章建議不要使用finalize方法)。這些攔截了finalize方法的增強(qiáng)類事例不會(huì)被垃圾回收器特別對(duì)待,同樣會(huì)被放入JVM的finalization隊(duì)里。如果你不小心在callback中硬編碼應(yīng)用了被增強(qiáng)類,那么你就創(chuàng)建了一個(gè)永遠(yuǎn)不被回收的實(shí)例。以上問題通常是你不希望發(fā)生的。慶幸的是,cglib不會(huì)代理所有的final方法,因此Object#wait, Object#notify和Object#notifyAll方法不會(huì)遇到這些問題。需要注意的是Object#clone是會(huì)被代理的,這通常是你不希望發(fā)生的。
Immutable Bean
cglib庫的ImmutableBean允許創(chuàng)建一個(gè)不可變的包裝器,該特性與Collections#immutableSet類似。所有要修改被包裝Bean的操作都會(huì)被阻止,并且拋出IllegalStateException異常(不是使用的java API建議的UnsupportedOperationException)。讓我們來看一些Bean:
public class SampleBean {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
讓我們來講這個(gè)Bean變成不可變:
@Test(expected = IllegalStateException.class)
public void testImmutableBean() throws Exception {
SampleBean bean = new SampleBean();
bean.setValue("Hello world!");
SampleBean immutableBean = (SampleBean) ImmutableBean.create(bean);
assertEquals("Hello world!", immutableBean.getValue());
bean.setValue("Hello world, again!");
assertEquals("Hello world, again!", immutableBean.getValue());
immutableBean.setValue("Hello cglib!"); // Causes exception.
}
從上面的事例可以明顯看出,Immutable bean阻止了對(duì)bean的任何狀態(tài)的修改,并且會(huì)拋出IllegalStateException異常。然而,通過原始的bean修改是可行的,通過所有對(duì)原始bean的修改會(huì)反映到Immutable bean中。
Bean Generator
Bean Generator是cglib提供的另一個(gè)強(qiáng)大的工具類,它可以實(shí)現(xiàn)在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建bean:
@Test
public void testBeanGenerator() throws Exception {
BeanGenerator beanGenerator = new BeanGenerator();
beanGenerator.addProperty("value", String.class);
Object myBean = beanGenerator.create();
Method setter = myBean.getClass().getMethod("setValue", String.class);
setter.invoke(myBean, "Hello cglib!");
Method getter = myBean.getClass().getMethod("getValue");
assertEquals("Hello cglib!", getter.invoke(myBean));
}
從上面的事例可以明顯看出,BeanGenerator會(huì)首先通過addProperty方法設(shè)置屬性名以及屬性類型,在創(chuàng)建階段,BeanGenerator會(huì)自動(dòng)為屬性創(chuàng)建getter和setter:
- <type> get<name>()
- void set<name>(<type>)
當(dāng)另一個(gè)庫期望通過反射解析到bean,但你在運(yùn)行時(shí)不知道這些bean時(shí),這可能很有用(這種場(chǎng)景的一個(gè)實(shí)例是Apache Wicket)。
Bean Copier
Bean Copier是另外一個(gè)工具類,這個(gè)工具類可以復(fù)制bean的屬性。假設(shè)現(xiàn)在有一個(gè)與SampleBean擁有相同屬性的bean:
public class OtherSampleBean {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
現(xiàn)在你就可以通過這個(gè)工具類拷貝這兩個(gè)bean的屬性值了:
@Test
public void testBeanCopier() throws Exception {
BeanCopier copier = BeanCopier.create(SampleBean.class, OtherSampleBean.class, false);
SampleBean bean = new SampleBean();
myBean.setValue("Hello cglib!");
OtherSampleBean otherBean = new OtherSampleBean();
copier.copy(bean, otherBean, null);
assertEquals("Hello cglib!", otherBean.getValue());
}
這種拷貝不受兩個(gè)bean的類型限制。BeanCopier#copy方法可以傳遞一個(gè)可選的參數(shù),這個(gè)參數(shù)可以用于更加細(xì)粒度的控制屬性拷貝邏輯。如果BeanCopier在通過create方法創(chuàng)建時(shí),第三個(gè)參數(shù)傳遞的false。則在調(diào)用copy方法時(shí),會(huì)忽略第三個(gè)參數(shù),所以可以直接傳null。
Bulk Bean
(該小節(jié)由譯者自行編寫)
通過BulkBean可以將繁瑣逐個(gè)屬性的方法調(diào)用,變成一個(gè)接口操作所有屬性的access方法,首先來看一個(gè)Bean:
public class MultiFieldBean {
private String name;
private String address;
private Integer age;
public MultiFieldBean() {
}
public MultiFieldBean(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
// 省略getter和setter
}
現(xiàn)在我們就可以使用BulkBean來操作MultiFieldBean了:
@Test
public void bulkBeanTest() {
BulkBean bulkBean = BulkBean.create(MultiFieldBean.class,
new String[]{"getName", "getAge", "getAddress"},
new String[]{"setName", "setAge", "setAddress"},
new Class[]{String.class, Integer.class, String.class}
);
MultiFieldBean bean = new MultiFieldBean("mallen", "chengdu", 18);
Object[] values = bulkBean.getPropertyValues(bean);
assertEquals("mallen", values[0]);
assertEquals(18, values[1]);
assertEquals("chengdu", values[2]);
// 設(shè)置屬性
bulkBean.setPropertyValues(bean, new Object[]{"mallen1", 19, "chengdu1"});
values = bulkBean.getPropertyValues(bean);
assertEquals("mallen1", values[0]);
assertEquals(19, values[1]);
assertEquals("chengdu1", values[2]);
}
BulkBean創(chuàng)建時(shí)需要使用到一個(gè)getter數(shù)組、一個(gè)setter數(shù)組和一個(gè)屬性類型的數(shù)組,創(chuàng)建后的代理對(duì)象就可以使用getPropertyValues和setPropertyValues方法操作屬性了。可以明顯的看出,這兩個(gè)方法操作的數(shù)組中的屬性順序與創(chuàng)建BulkBean時(shí)的順序是嚴(yán)格一致的。
Bean Map
這是cglib庫中的最后一個(gè)bean的工具類,這個(gè)工具類會(huì)將bean的所有屬性轉(zhuǎn)換為以String為key,Object為值的Map:
@Test
public void testBeanGenerator() throws Exception {
SampleBean bean = new SampleBean();
BeanMap map = BeanMap.create(bean);
bean.setValue("Hello cglib!");
assertEquals("Hello cglib", map.get("value"));
}
另外,可以使用BeanMap#newInstance(Object)方法,在不重新創(chuàng)建BeanMap實(shí)例的情況下轉(zhuǎn)換另外的bean實(shí)例。但是,另外的bean必須與創(chuàng)建BeanMap時(shí)指定的Bean是同一個(gè)Class實(shí)例。
Key Factory
KeyFactory可以用來動(dòng)態(tài)創(chuàng)建key,這些key可以由多個(gè)值組成。為了達(dá)到這個(gè)目的,KeyFactory需要一個(gè)接口,該接口用于定義組成key需要的值。這個(gè)接口需要有一個(gè)方法,方法名必須為newInstance,返回值必須為Object實(shí)例,比如:
public interface SampleKeyFactory {
Object newInstance(String first, int second);
}
有了接口后,就可以創(chuàng)建key的實(shí)例了:
@Test
public void testKeyFactory() throws Exception {
SampleKeyFactory keyFactory = (SampleKeyFactory) KeyFactory.create(Key.class);
Object key = keyFactory.newInstance("foo", 42);
Map<Object, String> map = new HashMap<Object, String>();
map.put(key, "Hello cglib!");
assertEquals("Hello cglib!", map.get(keyFactory.newInstance("foo", 42)));
}
KeyFactory會(huì)確保正確實(shí)現(xiàn)Object#equals(Object)和Object#hashCode方法,因此生成的key可以直接用于Map和set中。在cglib庫內(nèi)部,KeyFactory也是被頻繁使用的。
Mixin
有些人可能已經(jīng)從其他編程語言(如Ruby或Scala)中了解了Mixin類的概念,cglib中的Mixins允許將多個(gè)對(duì)象組合成一個(gè)對(duì)象。但是,這些對(duì)象必須要實(shí)現(xiàn)接口:
public interface Interface1 {
String first();
}
public interface Interface2 {
String second();
}
public class Class1 implements Interface1 {
@Override
public String first() {
return "first";
}
}
public class Class2 implements Interface2 {
@Override
public String second() {
return "second";
}
}
現(xiàn)在可以通過附加接口將類Class1和Class2組合到單個(gè)類中:
public interface MixinInterface extends Interface1, Interface2 {
/* empty */
}
@Test
public void testMixin() throws Exception {
Mixin mixin = Mixin.create(
new Class[]{Interface1.class, Interface2.class, MixinInterface.class},
new Object[]{new Class1(), new Class2()}
);
MixinInterface mixinDelegate = (MixinInterface) mixin;
assertEquals("first", mixinDelegate.first());
assertEquals("second", mixinDelegate.second());
}
不可否認(rèn),上面事例中的Mixin API相當(dāng)笨拙,因?yàn)樾枰~外的定義MixinInterface接口,這個(gè)問題可以通過非檢測(cè)的Java來解決(譯者注:可能是說類型強(qiáng)制轉(zhuǎn)換,直接將mixin轉(zhuǎn)換為Interface1或者Interface2)。
String Switcher
String Switcher可以將String模擬成int:
@Test
public void testStringSwitcher() throws Exception {
String[] strings = new String[]{"one", "two"};
int[] values = new int[]{10, 20};
StringSwitcher stringSwitcher = StringSwitcher.create(strings, values, true);
assertEquals(10, stringSwitcher.intValue("one"));
assertEquals(20, stringSwitcher.intValue("two"));
assertEquals(-1, stringSwitcher.intValue("three"));
}
StringSwitcher可以模擬switch的分支邏輯,就像java7或者更高版本已經(jīng)內(nèi)建的swtich一樣。如果在Java 6或更少的版本中使用StringSwitcher確實(shí)會(huì)給代碼帶來一些好處,但這是值得懷疑的,我個(gè)人是不建議使用的。
Interface Maker
就像InterfaceMaker的名字描述的一樣,這個(gè)類允許我們動(dòng)態(tài)的創(chuàng)建Interface:
@Test
public void testInterfaceMaker() throws Exception {
Signature signature = new Signature("foo", Type.DOUBLE_TYPE, new Type[]{Type.INT_TYPE});
InterfaceMaker interfaceMaker = new InterfaceMaker();
interfaceMaker.add(signature, new Type[0]);
Class iface = interfaceMaker.create();
assertEquals(1, iface.getMethods().length);
assertEquals("foo", iface.getMethods()[0].getName());
assertEquals(double.class, iface.getMethods()[0].getReturnType());
}
與其他的cglib庫的public API不同的是,interface maker依賴于ASM的類型。在一個(gè)運(yùn)行的程序中創(chuàng)建interface是激活沒有意義的,因?yàn)橐粋€(gè)interface僅僅代表著一個(gè)類型,一般是在編譯器的進(jìn)行類型檢測(cè)時(shí)使用。當(dāng)然,如果是你是要將生成的代碼用于后續(xù)的開發(fā),還是有一些用處的。
Method Delegate
MethodDelegate允許通過將方法調(diào)用綁定到某個(gè)接口來模擬類似于c#的方法委托,例如,下面的代碼將SampleBean#getValue方法綁定到委托:
public interface BeanDelegate {
String getValueFromDelegate();
}
@Test
public void testMethodDelegate() throws Exception {
SampleBean bean = new SampleBean();
bean.setValue("Hello cglib!");
BeanDelegate delegate = (BeanDelegate) MethodDelegate.create(
bean, "getValue", BeanDelegate.class);
assertEquals("Hello world!", delegate.getValueFromDelegate());
}
然而,有一些事情需要注意:
- 工廠方法MethodDelegate#create只接受一個(gè)方法名稱作為其第二個(gè)參數(shù),這個(gè)參數(shù)是MethodDelegate將為您代理的方法;
- MethodDelegate#create的第一個(gè)參數(shù),必須是一個(gè)包含無參方法的對(duì)象實(shí)例,由此可以看出MethodDelegate并沒有達(dá)到應(yīng)該有的強(qiáng)大程度;
- 第三個(gè)參數(shù)必須是一個(gè)interface,且該interface有且只能有一個(gè)方法(譯者注:與原文不一樣,原文寫的是有且僅有一個(gè)參數(shù),可能是原文筆者筆誤吧~~)。MethodDelegate會(huì)實(shí)現(xiàn)這個(gè)接口,并且可以將create的實(shí)例強(qiáng)制轉(zhuǎn)換為該接口。當(dāng)這個(gè)代理接口的方法被調(diào)用的時(shí)候,在其內(nèi)部會(huì)將調(diào)用代理到第一個(gè)參數(shù)的方法上。
除此之外,我們還可以考慮下MethodDelegate的缺點(diǎn):
- cglib會(huì)為每個(gè)代理創(chuàng)建一個(gè)新的類,最終會(huì)導(dǎo)致你的永久代(譯者注:保存類定義的內(nèi)存空間,java8之后不再將類定義保存在永久代,但同樣會(huì)影響到整機(jī)的內(nèi)存空間)內(nèi)存空間的浪費(fèi);
- 不能代理包含參數(shù)的方法;
- 如果你的接口方法包含參數(shù),Method Deletegate將不會(huì)正常的工作,但是它也不會(huì)拋出任何的異常信息(方法的返回值永遠(yuǎn)都是null)。如果你的接口方法的返回類型與被代理的方法不一致(即便是被代理方法返回對(duì)象的父類),你將會(huì)收到一個(gè)IllegalArgumentException異常。
Multicast Delegate
盡管MulticastDelegate與MethodDelegate要解決的目標(biāo)功能是一致的,但是二者之間的工作方式還是會(huì)有一些區(qū)別的。為了使用MulticastDelegate,我們的對(duì)象需要實(shí)現(xiàn)一個(gè)接口:
public interface DelegatationProvider {
void setValue(String value);
}
public class SimpleMulticastBean implements DelegatationProvider {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
基于這個(gè)實(shí)現(xiàn)了DelegateProvider接口的Bean,我們就可以創(chuàng)建一個(gè)DelegateProvider。這個(gè)DelegateProvider會(huì)將所有調(diào)用setValue(String)方法的請(qǐng)求,紛發(fā)到多個(gè)實(shí)現(xiàn)了DelegateProvider接口的類:
@Test
public void testMulticastDelegate() throws Exception {
MulticastDelegate multicastDelegate = MulticastDelegate.create(
DelegatationProvider.class);
SimpleMulticastBean first = new SimpleMulticastBean();
SimpleMulticastBean second = new SimpleMulticastBean();
multicastDelegate = multicastDelegate.add(first);
multicastDelegate = multicastDelegate.add(second);
DelegatationProvider provider = (DelegatationProvider)multicastDelegate;
provider.setValue("Hello world!");
assertEquals("Hello world!", first.getValue());
assertEquals("Hello world!", second.getValue());
}
同樣,MulticastDelegate也有一些缺點(diǎn):
- 所有的對(duì)象都需要實(shí)現(xiàn)一個(gè)包含一個(gè)方法的接口,這對(duì)于第三方庫來說很糟糕,當(dāng)你想要使用CGlib實(shí)現(xiàn)一些隱藏性的操作時(shí),是不可行的,實(shí)現(xiàn)這些操作的代碼就會(huì)暴露到正常的代碼中。其實(shí),你可以自己輕松的實(shí)現(xiàn)這種代理方式(盡管沒有使用到byte code框架,但是我猜你自己實(shí)現(xiàn)會(huì)更好)。
- 當(dāng)被代理方法需要返回?cái)?shù)據(jù)時(shí),你只能拿到最后一個(gè)對(duì)象返回的數(shù)據(jù),其他對(duì)象返回的數(shù)據(jù)都會(huì)丟失(但是可以在某些點(diǎn)被multicast delegate檢測(cè)到)。
Constructor Delegate
ConstructorDelegate允許創(chuàng)建一個(gè)以字節(jié)為單位的工廠方法,要使用ConstructorDelegate首先需要一個(gè)接口,這個(gè)接口必須包含一個(gè)名稱為newInstance的方法,該方法的返回值必須為Object,這個(gè)方法可以包含任意多個(gè)參數(shù)(參數(shù)個(gè)數(shù)和類型與需要代理的構(gòu)造方法相同)。例如,為了為SampleBean創(chuàng)建ConstructorDelegate,我們需要以下內(nèi)容來調(diào)用SampleBean的默認(rèn)(無參數(shù))構(gòu)造函數(shù):
public interface SampleBeanConstructorDelegate {
Object newInstance();
}
@Test
public void testConstructorDelegate() throws Exception {
SampleBeanConstructorDelegate constructorDelegate = (SampleBeanConstructorDelegate) ConstructorDelegate.create(
SampleBean.class, SampleBeanConstructorDelegate.class);
SampleBean bean = (SampleBean) constructorDelegate.newInstance();
assertTrue(SampleBean.class.isAssignableFrom(bean.getClass()));
}
Parallel Sorter
在排序數(shù)組數(shù)組時(shí),ParallelSorter聲稱是Java標(biāo)準(zhǔn)庫的數(shù)組排序器的更快替代品:
@Test
public void testParallelSorter() throws Exception {
Integer[][] value = {
{4, 3, 9, 0},
{2, 1, 6, 0}
};
ParallelSorter.create(value).mergeSort(0);
for(Integer[] row : value) {
int former = -1;
for(int val : row) {
assertTrue(former < val);
former = val;
}
}
}
ParallelSorter創(chuàng)建時(shí)使用的是二維數(shù)組(譯者注:使用二維數(shù)組主要是為了將要排序的數(shù)組作為一個(gè)集合統(tǒng)一傳入),然后就可以第二級(jí)數(shù)組(子數(shù)組)進(jìn)行歸并排序和快速排序。然而,在使用時(shí)你需要注意:
- 當(dāng)對(duì)原始類型進(jìn)行排序時(shí),你只能使用mergeSort方法的重載方法,手動(dòng)指定排序范圍(比如:e.g. ParallelSorter.create(value).mergeSort(0, 0, 4), 其中4表示排序數(shù)組的長度),否則ParallelSorter出現(xiàn)一個(gè)明顯的bug,因?yàn)镻arallelSorter(在獲取最大index時(shí))會(huì)將強(qiáng)制轉(zhuǎn)換為Object數(shù)組,導(dǎo)致ClassCastException異常;
- 如果被排序的數(shù)組的長度不一致,mergeSort的第一個(gè)參數(shù)會(huì)決定采用哪一行的長度作為參考長度(譯者注:3.2.10版本不是這樣的,它始終使用第一行作為長度參考)。比參考長度短的行會(huì)導(dǎo)致ArrayIndexOutOfBoundException異常,比參考長度長的行超出位置的數(shù)字不會(huì)被排序。
就我個(gè)人而言,我懷疑ParallelSorter是否真的在排序時(shí)間上有優(yōu)勢(shì)。誠然,我還沒有嘗試對(duì)它進(jìn)行基準(zhǔn)測(cè)試。如果你嘗試過,我很樂意在評(píng)論中聽到你的回復(fù)。
Fast Class and Fast Members
FastClass承諾要成為一個(gè)比Java reflection API更快速的工具類,它包裝一個(gè)Class,并提供與Java reflection API相同API:
@Test
public void testFastClass() throws Exception {
FastClass fastClass = FastClass.create(SampleBean.class);
FastMethod fastMethod = fastClass.getMethod(SampleBean.class.getMethod("getValue"));
MyBean myBean = new MyBean();
myBean.setValue("Hello cglib!");
assertTrue("Hello cglib!", fastMethod.invoke(myBean, new Object[0]));
}
除了FastMethod外,還可以創(chuàng)建FastConstructor,但是不能創(chuàng)建fast field。但是,F(xiàn)astClass是怎么樣實(shí)現(xiàn)的以至于比Java reflection API快呢?Java reflection是通過JNI(Java Native Interface)執(zhí)行的,JNI會(huì)調(diào)用C語言的代碼執(zhí)行反射方法,而FastClass是通過生成一些字節(jié)碼直接在JVM中調(diào)用的。然而,新版本的HotSpot JVM(或許還有其他的現(xiàn)代JVM)會(huì)有一個(gè)叫做inflation的概念,當(dāng)使用JNI調(diào)用超過一定次數(shù)后會(huì)生成一個(gè)本地版本的FasClass字節(jié)碼。你通過屬性sun.reflect.inflationThreshold設(shè)置這個(gè)次數(shù)(默認(rèn)為15次),以控制jvm的inflation行為(至少在HotSpot JVM中)。這個(gè)屬性決定了在執(zhí)行多少次JNI調(diào)用后會(huì)在本地生成字節(jié)碼。我建議在現(xiàn)代的JVM中不再使用FastClass,但是在老版本的JVM中可以用來優(yōu)化性能。
cglib Proxy
就像本文開頭個(gè)所說,cglib Proxy是Java Proxy的重新實(shí)現(xiàn)版本。開發(fā)這個(gè)庫的本意是想要在Java 1.3之前的版本中使用Java庫的proxy,當(dāng)時(shí)的cglib Proxy與Java Proxy僅有很少的細(xì)節(jié)上有區(qū)別。在Java Standard庫的javadoc中有關(guān)于Java Proxy很好的文檔,在此我將省略對(duì)其的細(xì)節(jié)討論。
最后的警告
在概述了cglib的功能后,我想要在最后說一句警告的話。cglib在生成字節(jié)碼時(shí)還會(huì)額外的生成一些類,這些類都會(huì)被保存在JVM的一塊特殊內(nèi)存中:所謂的perm space。就像他的名字一樣,這塊永久的內(nèi)存空間適用于保存永久對(duì)象的,這些對(duì)象將不會(huì)被垃圾回收器回收。然后,這也不是完全正確的:當(dāng)一個(gè)類被加載(load)后,如果加載它的ClassLoader沒有準(zhǔn)備好進(jìn)行垃圾回收,它就不會(huì)被卸載(unload)。ClassLoader被回收的唯一場(chǎng)景是,這個(gè)ClassLoader不是JVM系統(tǒng)的ClassLoader而是自定義的(程序創(chuàng)建的)ClassLoader。這種ClassLoader如果自己準(zhǔn)備好,且它加載的所有類以及這些類的實(shí)例都準(zhǔn)備好回收了,垃圾回收器才會(huì)真正的回收。這就意味著,如果你在Java程序中創(chuàng)建越來越多的類,并且不認(rèn)證考慮移除內(nèi)存中的這些類,你遲早會(huì)將perm space耗盡,最終程序死在OutOfMemoryError之手。因此,請(qǐng)謹(jǐn)慎使用cglib。但是,如果你明知且謹(jǐn)慎的使用cglib,你將可以做很多純Java程序不能做的奇妙的事情。
最后,當(dāng)你創(chuàng)建依賴于cglib的項(xiàng)目的時(shí)候,你需要注意到一個(gè)事實(shí):cglib項(xiàng)目沒有得到應(yīng)有的管理和開發(fā)積極性(考慮到它的流行程度來說)。缺少文檔就是這么說的第一個(gè)證據(jù),第二個(gè)就是它經(jīng)常出現(xiàn)的凌亂的public接口。發(fā)布到Maven中心倉庫也存在不好的地方,郵件列表就像是垃圾郵件一樣,它的版本迭代也是相當(dāng)?shù)牟环€(wěn)定。因此你可能需要了解一下javassist,一個(gè)真正可以替代cglib的庫(但是功能較cglib弱)。Javassist附帶了一個(gè)偽Java編譯器,這樣就可以在不清楚Java字節(jié)碼的情況下創(chuàng)建不可思議的字節(jié)碼增強(qiáng)了。如果你想要親力親為,你可能更喜歡ASM(cglib就是基于這個(gè)構(gòu)建的),ASM不管是庫還是Java代碼又或者是字節(jié)碼,都有強(qiáng)大的文檔支持。
需要注意的是,本文中的所有事例都只能運(yùn)行在cglib 2.2.2中,與新的3.x的版本不兼容。不幸的是,我體驗(yàn)了cglib的最新版本,但是它偶爾會(huì)生成一些無效的字節(jié)碼,所以我現(xiàn)在在生產(chǎn)環(huán)境中使用的還是老版本。另外一個(gè)需要注意的問題是,大多數(shù)使用cglig的項(xiàng)目都將cglib轉(zhuǎn)移到了他們自己的namespace下,以防止與其他依賴包的版本沖突,比如Spring project。建議你在使用cglib的時(shí)候也這么做,很多工具可以幫助你自動(dòng)完成這一最佳實(shí)踐,比如jarjar。