cglib:缺少的手册
字節(jié)碼檢測(cè)庫cglib是許多眾所周知的Java框架(例如Hibernate (現(xiàn)在不再 ))或Spring最受歡迎的選擇,它們可以完成骯臟的工作。 字節(jié)碼檢測(cè)允許在Java應(yīng)用程序的編譯階段之后操作或創(chuàng)建類。 由于Java類在運(yùn)行時(shí)動(dòng)態(tài)鏈接,因此可以將新類添加到已經(jīng)運(yùn)行的Java程序中。 Hibernate例如使用cglib生成動(dòng)態(tài)代理。 Hibernate不會(huì)返回您存儲(chǔ)在數(shù)據(jù)庫中的完整對(duì)象,而是會(huì)返回存儲(chǔ)類的檢測(cè)版本,該版本僅在需要時(shí)才從數(shù)據(jù)庫延遲加載某些值。 例如,在向方法調(diào)用添加安全約束時(shí),Spring使用了cglib。 而不是直接調(diào)用您的方法,Spring安全性將首先檢查指定的安全性檢查是否通過,并且僅在驗(yàn)證之后委托給您的實(shí)際方法。 cglib的另一種流行用法是在諸如mockito之類的模擬框架內(nèi),其中模擬只不過是插裝類 ,在插裝類中,方法被空的實(shí)現(xiàn)(加上一些跟蹤邏輯)所替代。
除了ASM (另一個(gè)基于cglib的非常高級(jí)的字節(jié)碼操作庫)之外,cglib還提供了相當(dāng)?shù)图?jí)的字節(jié)碼轉(zhuǎn)換器,即使不了解已編譯的Java類的詳細(xì)信息,也可以使用它們。 不幸的是,cglib的文檔很短,并不是說基本上沒有。 除了2005年的一篇博客文章演示了Enhancer類之外,沒有太多可找的了。 這篇博客文章是試圖演示cglib及其不幸的是常常尷尬的API。
增強(qiáng)劑
讓我們從Enhancer類(cglib庫中最常用的類)開始。 增強(qiáng)程序允許為非接口類型創(chuàng)建Java代理。 可以將Enhancer器與Java標(biāo)準(zhǔn)庫的Proxy類(在Java 1.3中引入)進(jìn)行比較。 Enhancer動(dòng)態(tài)創(chuàng)建給定類型的子類,但攔截所有方法調(diào)用。 除Proxy類外,它對(duì)類和接口類型均適用。 以下示例和下面的一些示例均基于此簡單的Java POJO:
public static class SampleClass {public String test(String input) {return "Hello world!";} }使用cglib,可以使用Enhancer和FixedValue回調(diào)輕松地將test(String)方法的返回值替換為另一個(gè)值:
@Test public void testFixedValue() throws Exception {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(SampleClass.class);enhancer.setCallback(new FixedValue() {@Overridepublic Object loadObject() throws Exception {return "Hello cglib!";}});SampleClass proxy = (SampleClass) enhancer.create();assertEquals("Hello cglib!", proxy.test(null)); }在上面的示例中,增強(qiáng)器將返回SampleClass的已檢測(cè)子類的實(shí)例,其中所有方法調(diào)用均返回固定值,該值是由上面的匿名FixedValue實(shí)現(xiàn)生成的。 該對(duì)象由Enhancer#create(Object...) ,其中該方法采用任意數(shù)量的參數(shù),這些參數(shù)用于選擇增強(qiáng)類的任何構(gòu)造函數(shù)。 (即使構(gòu)造函數(shù)只是Java字節(jié)碼級(jí)別上的方法, Enhancer類也不能檢測(cè)構(gòu)造函數(shù)。它也不能檢測(cè)static或final類。)如果只想創(chuàng)建一個(gè)類,而又不想創(chuàng)建實(shí)例, Enhancer#createClass將創(chuàng)建一個(gè)Class實(shí)例,可用于動(dòng)態(tài)創(chuàng)建實(shí)例。 增強(qiáng)類的所有構(gòu)造函數(shù)都可以在此動(dòng)態(tài)生成的類中用作委托構(gòu)造函數(shù)。
請(qǐng)注意,在上面的示例中,將委派任何方法調(diào)用,還應(yīng)調(diào)用java.lang.Object定義的方法。 結(jié)果,對(duì)proxy.toString()的調(diào)用也將返回“ Hello cglib!”。 相比之下,對(duì)proxy.hashCode()的調(diào)用將導(dǎo)致ClassCastException因?yàn)榧词筄bject#hashCode簽名需要原始整數(shù), FixedValue攔截器也始終返回String 。
可以得出的另一個(gè)結(jié)論是最終方法沒有被攔截。 這種方法的一個(gè)示例是Object#getClass ,在調(diào)用該方法時(shí)將返回類似“ SampleClass $$ EnhancerByCGLIB $$ e277c63c”的內(nèi)容。 此類名稱由cglib隨機(jī)生成,以避免命名沖突。 在程序代碼中使用顯式類型時(shí),請(qǐng)注意增強(qiáng)型實(shí)例的不同類。 但是,由cglib生成的類將與增強(qiáng)類位于同一包中(因此可以覆蓋package-private方法)。 與最終方法類似,子類化方法導(dǎo)致無法增強(qiáng)最終類。 因此,像Hibernate這樣的框架無法持久化最終類。
接下來,讓我們看一個(gè)更強(qiáng)大的回調(diào)類InvocationHandler ,它也可以與Enhancer一起使用:
@Test public void testInvocationHandler() throws Exception {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(SampleClass.class);enhancer.setCallback(new InvocationHandler() {@Overridepublic 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()); }該回調(diào)使我們可以對(duì)調(diào)用的方法進(jìn)行回答。 但是,在InvocationHandler#invoke方法隨附的代理對(duì)象上調(diào)用方法時(shí)應(yīng)小心。 將使用相同的InvocationHandler調(diào)度對(duì)此方法的所有調(diào)用,因此可能導(dǎo)致無限循環(huán)。 為了避免這種情況,我們可以使用另一個(gè)回調(diào)分配器:
@Test public void testMethodInterceptor() throws Exception {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(SampleClass.class);enhancer.setCallback(new MethodInterceptor() {@Overridepublic 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允許完全控制所攔截的方法,并提供一些實(shí)用程序來以其原始狀態(tài)調(diào)用增強(qiáng)類的方法。 但是為什么仍然要使用其他方法呢? 因?yàn)槠渌椒ㄐ矢?#xff0c;并且cglib通常用于效率起著重要作用的邊緣案例框架。 MethodInterceptor的創(chuàng)建和鏈接需要例如生成不同類型的字節(jié)碼以及創(chuàng)建InvocationHandler不需要的某些運(yùn)行時(shí)對(duì)象。 因此,可以將其他類與增強(qiáng)器一起使用:
- LazyLoader :即使LazyLoader的唯一方法具有相同的方法簽名FixedValue的LazyLoader是在根本不同F(xiàn)ixedValue攔截。 LazyLoader實(shí)際上應(yīng)該返回增強(qiáng)類的子類的實(shí)例。 僅當(dāng)在增強(qiáng)型對(duì)象上調(diào)用方法并將其存儲(chǔ)以供將來調(diào)用生成的代理時(shí),才請(qǐng)求此實(shí)例。 如果您的對(duì)象創(chuàng)建昂貴而又不知道該對(duì)象是否會(huì)被使用,則這是有道理的。 請(qǐng)注意,必須同時(shí)為代理對(duì)象和延遲加載的對(duì)象調(diào)用增強(qiáng)類的某些構(gòu)造函數(shù)。 因此,請(qǐng)確保有另一個(gè)廉價(jià)的(可能protected )構(gòu)造函數(shù)可用,或?qū)⒔涌陬愋陀米鞔怼?您可以通過將參數(shù)提供給Enhancer#create(Object...)來選擇構(gòu)造的被調(diào)用方法。
- Dispatcher : Dispatcher類似于LazyLoader但將在每次方法調(diào)用時(shí)調(diào)用,而不存儲(chǔ)已加載的對(duì)象。 這允許更改類的實(shí)現(xiàn)而無需更改對(duì)它的引用。 同樣,請(qǐng)注意必須同時(shí)為代理和生成的對(duì)象調(diào)用某些構(gòu)造函數(shù)。
- ProxyRefDispatcher :此類包含對(duì)在其簽名中調(diào)用的代理對(duì)象的引用。 例如,這允許將方法調(diào)用委托給此代理的另一個(gè)方法。 請(qǐng)注意,如果從ProxyRefDispatcher#loadObject(Object)內(nèi)調(diào)用相同的方法,這很容易導(dǎo)致無限循環(huán),并且始終會(huì)導(dǎo)致無限循環(huán)。
- NoOp : NoOp類與其名稱不符。 而是將每個(gè)方法調(diào)用委派給增強(qiáng)類的方法實(shí)現(xiàn)。
此時(shí),最后兩個(gè)攔截器可能對(duì)您沒有意義。 當(dāng)總是將方法調(diào)用始終委派給增強(qiáng)類時(shí),為什么還要增強(qiáng)類呢? 你是對(duì)的。 這些攔截器僅應(yīng)與CallbackFilter一起使用,如以下代碼片段所示:
@Test public void testCallbackFilter() throws Exception {Enhancer enhancer = new Enhancer();CallbackHelper callbackHelper = new CallbackHelper(SampleClass.class, new Class[0]) {@Overrideprotected Object getCallback(Method method) {if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {return new FixedValue() {@Overridepublic 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實(shí)例在其Enhancer#setCallbackFilter(CallbackFilter)方法中接受CallbackFilter在此方法中,它希望將增強(qiáng)類的方法映射到Callback實(shí)例數(shù)組的數(shù)組索引。 在創(chuàng)建的代理上調(diào)用方法時(shí), Enhancer將選擇相應(yīng)的攔截器,并將調(diào)用的方法分派到相應(yīng)的Callback (這是到目前為止引入的所有攔截器的標(biāo)記接口)。 為了使該API不再那么笨拙,cglib提供了一個(gè)CallbackHelper ,它將代表一個(gè)CallbackFilter并可以為您創(chuàng)建一個(gè)Callback數(shù)組。 上面的增強(qiáng)對(duì)象在功能上與MethodInterceptor示例中的對(duì)象等效,但是它使您可以編寫專用的攔截器,同時(shí)將對(duì)這些攔截器的調(diào)度邏輯分開。
它是如何工作的?
Enhancer創(chuàng)建類時(shí),它將為創(chuàng)建后為增強(qiáng)類注冊(cè)為Callback每個(gè)攔截器設(shè)置一個(gè)創(chuàng)建private static字段。 這也意味著用cglib創(chuàng)建的類定義在創(chuàng)建后就不能重用,因?yàn)榛卣{(diào)的注冊(cè)不會(huì)成為所生成類的初始化階段的一部分,而是由JVM初始化該類后由cglib手動(dòng)準(zhǔn)備的。 這也意味著用cglib創(chuàng)建的類在初始化后在技術(shù)上還沒有準(zhǔn)備好,例如由于目標(biāo)計(jì)算機(jī)中加載的類不存在回調(diào),因此無法通過電線發(fā)送。
取決于注冊(cè)攔截器,CGLIB可能記錄附加字段,諸如例如用于MethodInterceptor其中兩個(gè)private static字段(一個(gè)保持的反射Method和另一保持MethodProxy是在增強(qiáng)類或任何的截取)的每方法注冊(cè)它的子類。 請(qǐng)注意, MethodProxy過度使用了FastClass ,這會(huì)觸發(fā)其他類的創(chuàng)建,下面將對(duì)其進(jìn)行詳細(xì)描述。
由于所有這些原因,使用Enhancer時(shí)要小心。 并且始終要防御性地注冊(cè)回調(diào)類型,因?yàn)槔鏜ethodInterceptor將觸發(fā)創(chuàng)建其他類并在增強(qiáng)類中注冊(cè)其他static字段。 這特別危險(xiǎn),因?yàn)榛卣{(diào)變量也作為static變量存儲(chǔ)在增強(qiáng)的類中:這意味著回調(diào)實(shí)例永遠(yuǎn)不會(huì)被垃圾回收(除非它們的ClassLoader是異常的)。 當(dāng)使用匿名類對(duì)它們的外部類進(jìn)行靜默引用時(shí),這特別危險(xiǎn)。 回想一下上面的例子:
@Test public void testFixedValue() throws Exception {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(SampleClass.class);enhancer.setCallback(new FixedValue() {@Overridepublic Object loadObject() throws Exception {return "Hello cglib!";}});SampleClass proxy = (SampleClass) enhancer.create();assertEquals("Hello cglib!", proxy.test(null)); }FixedValue的匿名子類幾乎無法從增強(qiáng)的SampleClass引用,因此匿名的FixedValue實(shí)例或包含@Test方法的類都不會(huì)被垃圾回收。 這會(huì)在您的應(yīng)用程序中引入討厭的內(nèi)存泄漏。 因此,請(qǐng)勿將非static內(nèi)部類與cglib一起使用。 (為了使示例簡短,我僅在此博客條目中使用它們。)
最后,永遠(yuǎn)不要攔截Object#finalize() 。 由于cglib的子類化方法,截取finalize函數(shù)是通過覆蓋它來實(shí)現(xiàn)的,通常這是一個(gè)壞主意 。 攔截終結(jié)器的增強(qiáng)型實(shí)例將由垃圾收集器以不同的方式處理,并且還將導(dǎo)致這些對(duì)象在JVM的終結(jié)器隊(duì)列中排隊(duì)。 另外,如果您(偶然)在截獲的finalize調(diào)用中創(chuàng)建了對(duì)增強(qiáng)類的硬引用,則實(shí)際上已經(jīng)創(chuàng)建了一個(gè)不可收集的實(shí)例。 通常,這不是您想要的。 請(qǐng)注意, final方法永遠(yuǎn)不會(huì)被cglib攔截。 因此, Object#wait , Object#notify和Object#notifyAll不會(huì)帶來相同的問題。 但是請(qǐng)注意, Object#clone可能會(huì)被攔截,這是您可能不想執(zhí)行的操作。
不變的豆
cglib的ImmutableBean允許您創(chuàng)建一個(gè)不可變包裝器,類似于Collections#immutableSet 。 IllegalStateException (但是,不是Java API建議的UnsupportedOperationException可以防止對(duì)基礎(chǔ)bean進(jìn)行所有更改。 看著一些豆
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. }從該示例可以明顯看出,不可變bean通過拋出IllegalStateException防止所有狀態(tài)更改。 但是,可以通過更改原始對(duì)象來更改Bean的狀態(tài)。 所有這些更改都將通過ImmutableBean反映出來。
豆產(chǎn)生器
BeanGenerator是cglib的另一個(gè)bean實(shí)用程序。 它將在運(yùn)行時(shí)為您創(chuàng)建一個(gè)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 , BeanGenerator首先將一些屬性用作名稱/值對(duì)。 創(chuàng)建時(shí), BeanGenerator創(chuàng)建訪問器
- <type> get<name>()
- void set<name>(<type>)
為了你。 當(dāng)另一個(gè)庫期望通過反射來解析的bean,但是您在運(yùn)行時(shí)不知道這些bean時(shí),這可能很有用。 (一個(gè)示例是Apache Wicket ,它可以與bean一起使用。)
豆復(fù)印機(jī)
BeanCopier是另一個(gè)Bean實(shí)用程序,可通過其屬性值復(fù)制Bean。 考慮另一個(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è)bean復(fù)制到另一個(gè):
@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()); }不受特定類型的限制。 BeanCopier#copy方法可以(最終)選擇一個(gè)Converter ,它允許對(duì)每個(gè)bean屬性進(jìn)行一些進(jìn)一步的操作。 如果BeanCopier是使用false作為第三個(gè)構(gòu)造函數(shù)參數(shù)創(chuàng)建的,則Converter被忽略,因此可以為null 。
散裝豆
BulkBean允許通過數(shù)組而不是方法調(diào)用使用一組指定的bean訪問器:
@Test public void testBulkBean() throws Exception {BulkBean bulkBean = BulkBean.create(SampleBean.class,new String[]{"getValue"},new String[]{"setValue"},new Class[]{String.class});SampleBean bean = new SampleBean();bean.setValue("Hello world!");assertEquals(1, bulkBean.getPropertyValues(bean).length);assertEquals("Hello world!", bulkBean.getPropertyValues(bean)[0]);bulkBean.setPropertyValues(bean, new Object[] {"Hello cglib!"});assertEquals("Hello cglib!", bean.getValue()); }BulkBean將getter名稱數(shù)組,setter名稱數(shù)組和屬性類型數(shù)組作為其構(gòu)造函數(shù)參數(shù)。 然后,可以通過BulkBean#getPropertyBalues(Object)將生成的檢測(cè)類提取為數(shù)組。 同樣,可以通過BulkBean#setPropertyBalues(Object, Object[])設(shè)置bean的屬性。
豆地圖
這是cglib庫中的最后一個(gè)bean實(shí)用程序。 BeanMap將bean的所有屬性轉(zhuǎn)換為String to- Object Java 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)方法允許通過重用相同的Class為其他bean創(chuàng)建映射。
重點(diǎn)工廠
KeyFactory工廠允許動(dòng)態(tài)創(chuàng)建由多個(gè)值組成的鍵,這些值可以在例如Map實(shí)現(xiàn)中使用。 為此, KeyFactory需要一些接口來定義應(yīng)在此類鍵中使用的值。 此接口必須包含一個(gè)名為newInstance的方法,該方法返回Object 。 例如:
public interface SampleKeyFactory {Object newInstance(String first, int second); }現(xiàn)在可以通過以下方式創(chuàng)建一個(gè)aa密鑰的實(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將確保Object#equals(Object)和Object#hashCode方法的正確實(shí)現(xiàn),以便可以在Map或Set使用生成的鍵對(duì)象。 在cglib庫中, KeyFactory在內(nèi)部也有很多使用。
混合蛋白
有些人可能已經(jīng)從其他編程語言(例如Ruby或Scala,其中mixin稱為特征)中了解了Mixin類的概念。 cglib Mixin允許將多個(gè)對(duì)象組合成一個(gè)對(duì)象。 但是,為此,這些對(duì)象必須由接口支持:
public interface Interface1 {String first(); }public interface Interface2 {String second(); }public class Class1 implements Interface1 {@Overridepublic String first() {return "first";} }public class Class2 implements Interface2 {@Overridepublic 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()); }誠然, Mixin API相當(dāng)笨拙,因?yàn)樗枰糜贛ixin的類來實(shí)現(xiàn)某些接口,以便非儀表Java也可以解決該問題。
字符串切換器
StringSwitcher將String模擬為int Java Map :
@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允許在String上模擬switch命令,例如自Java 7起就可以使用內(nèi)置的Java switch語句來實(shí)現(xiàn)。如果在Java 6或更低StringSwitcher中使用StringSwitcher確實(shí)為您的代碼增加了好處,但是仍然值得懷疑,我會(huì)個(gè)人不建議使用它。
接口制造商
InterfaceMaker會(huì)執(zhí)行其名稱所建議的操作:它動(dòng)態(tài)創(chuàng)建一個(gè)新接口。
@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的任何其他公共API類之外,接口制造商還依賴于ASM類型。 在運(yùn)行的應(yīng)用程序中創(chuàng)建接口幾乎沒有意義,因?yàn)榻涌趦H表示一種類型,編譯器可以使用該類型來檢查類型。 但是,當(dāng)您生成要在以后的開發(fā)中使用的代碼時(shí),這可能很有意義。
方法委托
通過將方法調(diào)用綁定到某個(gè)接口, MethodDelegate可以將C#類的委托模擬為特定方法。 例如,以下代碼會(huì)將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()); }但是,有一些注意事項(xiàng):
- 工廠方法MethodDelegate#create恰好將一個(gè)方法名稱作為第二個(gè)參數(shù)。 這是MethodDelegate將為您代理的方法。
- 必須有一個(gè)沒有為對(duì)象定義參數(shù)的方法,該方法作為第一個(gè)參數(shù)提供給工廠方法。 因此, MethodDelegate強(qiáng)度不如可能強(qiáng)。
- 第三個(gè)參數(shù)必須是僅包含一個(gè)參數(shù)的接口。 MethodDelegate實(shí)現(xiàn)此接口,并且可以MethodDelegate為該接口。 調(diào)用該方法時(shí),它將在作為第一個(gè)參數(shù)的對(duì)象上調(diào)用代理方法。
此外,請(qǐng)考慮以下缺點(diǎn):
- cglib為每個(gè)代理創(chuàng)建一個(gè)新類。 最終,這會(huì)浪費(fèi)您永久的一代堆空間
- 您不能代理帶有參數(shù)的方法。
- 如果您的接口帶有參數(shù),則在沒有引發(fā)異常的情況下方法委托將根本無法工作(返回值始終為null )。 如果您的接口需要其他返回類型(即使是更通用的返回類型),則將收到IllegalArgumentException 。
組播代表
MulticastDelegate工作方式與MethodDelegate略有不同,即使它的目標(biāo)是相似的功能。 為了使用MulticastDelegate ,我們需要一個(gè)實(shí)現(xiàn)接口的對(duì)象:
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;} }基于此接口支持的bean,我們可以創(chuàng)建一個(gè)MulticastDelegate ,將對(duì)setValue(String)所有調(diào)用分派到實(shí)現(xiàn)DelegationProvider接口的幾個(gè)類:
@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()); }再次,有一些缺點(diǎn):
- 對(duì)象需要實(shí)現(xiàn)單方法接口。 這對(duì)于第三方庫來說很糟糕,并且當(dāng)您使用CGlib進(jìn)行某些魔術(shù)操作 (該魔術(shù)暴露于常規(guī)代碼)時(shí)很尷尬。 另外,您可以輕松實(shí)現(xiàn)自己的委托(盡管沒有字節(jié)碼,但我懷疑您在手動(dòng)委托方面是否能贏得如此之多)。
- 當(dāng)您的代表返回一個(gè)值時(shí),您將僅收到您添加的最后一個(gè)代表的值。 所有其他返回值都將丟失(但在某些時(shí)候由多播委托檢索)。
建設(shè)者代表
ConstructorDelegate允許創(chuàng)建字節(jié)儀表工廠方法 。 為此,我們首先需要一個(gè)具有單一方法newInstance的接口,該方法返回一個(gè)Object并采用任意數(shù)量的參數(shù)以用于指定類的構(gòu)造函數(shù)調(diào)用。 例如,為了為SampleBean創(chuàng)建一個(gè)ConstructorDelegate ,我們需要以下代碼來調(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())); }平行分選機(jī)
當(dāng)對(duì)數(shù)組數(shù)組進(jìn)行排序時(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接受一個(gè)數(shù)組數(shù)組,并允許對(duì)數(shù)組的每一行應(yīng)用合并排序或快速排序。 使用時(shí)請(qǐng)小心:
- 當(dāng)使用基本數(shù)組時(shí),您必須在示例中調(diào)用具有明確排序范圍的合并排序(例如, ParallelSorter.create(value).mergeSort(0, 0, 3) ,否則, ParallelSorter出現(xiàn)一個(gè)很明顯的錯(cuò)誤,即試圖將原始數(shù)組轉(zhuǎn)換為Object[]數(shù)組將導(dǎo)致ClassCastException 。
- 如果數(shù)組行不均勻,則第一個(gè)參數(shù)將確定要考慮的行的長度。 不均勻的行將導(dǎo)致不考慮多余的值進(jìn)行排序,或者導(dǎo)致ArrayIndexOutOfBoundException 。
就我個(gè)人而言,我懷疑ParallelSorter確實(shí)具有時(shí)間優(yōu)勢(shì)。 誠然,我還沒有嘗試對(duì)其進(jìn)行基準(zhǔn)測(cè)試。 如果您嘗試過,很高興在評(píng)論中聽到它。
快速班和快速成員
通過包裝Java類并提供與反射API類似的方法, FastClass承諾比Java反射API更快地調(diào)用方法:
@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 , FastClass還可以創(chuàng)建FastConstructor但不能創(chuàng)建快速字段。 但是FastClass如何比正常反射更快? Java反射由JNI執(zhí)行,其中方法調(diào)用由某些C代碼執(zhí)行。 FastClass創(chuàng)建一些字節(jié)代碼,這些代碼直接從JVM內(nèi)部調(diào)用該方法。 但是,HotSpot JVM的較新版本(可能還有許多其他現(xiàn)代JVM)都知道一個(gè)稱為膨脹的概念,在該概念中,當(dāng)反射方法經(jīng)常執(zhí)行時(shí),JVM會(huì)將反射方法調(diào)用轉(zhuǎn)換為FastClass 本機(jī)版本 。 您甚至可以通過將sun.reflect.inflationThreshold屬性設(shè)置為較低的值來控制此行為(至少在HotSpot JVM上)。 (默認(rèn)值為15。)此屬性確定在執(zhí)行了幾次反射調(diào)用后,應(yīng)使用字節(jié)碼檢測(cè)版本替換JNI調(diào)用。 因此,我建議不要在現(xiàn)代JVM上使用FastClass ,但是它可以調(diào)整舊Java虛擬機(jī)上的性能。
cglib代理
cglib Proxy是本文開頭提到的Java Proxy類的重新實(shí)現(xiàn)。 它旨在允許在Java 1.3之前的Java版本中使用Java庫的代理,并且僅在次要細(xì)節(jié)上有所不同。 但是,可以在Java標(biāo)準(zhǔn)庫的Proxy javadoc中找到cglib Proxy的更好文檔,其中提供了其用法示例。 因此,我將在這里跳過對(duì)cglib Proxy的更詳細(xì)的討論。
最后的警告
在對(duì)cglib功能進(jìn)行了概述之后,我想說最后一個(gè)警告。 所有cglib類都會(huì)生成字節(jié)碼,這會(huì)導(dǎo)致其他類存儲(chǔ)在JVM內(nèi)存的特殊部分中:所謂的燙發(fā)空間。 顧名思義,該永久空間用于通常不收集垃圾的永久對(duì)象。 但是,這不是完全正確的:加載Class ,在加載的ClassLoader可用于垃圾回收之前,無法將其卸載。 僅在用自定義ClassLoader加載Class的情況下,該ClassLoader不是本機(jī)JVM系統(tǒng)ClassLoader 。 這個(gè)ClassLoader可以,如果本身,都被垃圾收集Class ES IT不斷加載,并且所有的所有實(shí)例Class ES IT負(fù)載曾經(jīng)成為可進(jìn)行垃圾回收。 這意味著:如果您在Java應(yīng)用程序的整個(gè)生命周期中創(chuàng)建了越來越多的類,并且如果您不注意刪除這些類,那么您將早晚運(yùn)行燙發(fā)空間,這將導(dǎo)致您的應(yīng)用程序因運(yùn)行失敗而死亡。 OutOfMemoryError手中 。 因此,請(qǐng)謹(jǐn)慎使用cglib。 但是,如果您明智且謹(jǐn)慎地使用cglib,則可以用它做真正令人驚奇的事情,這超出了非儀器化Java應(yīng)用程序可以做的事情。
最后,在創(chuàng)建依賴cglib的項(xiàng)目時(shí),考慮到它的普及性,您應(yīng)該意識(shí)到cglib項(xiàng)目沒有得到應(yīng)有的維護(hù)和活動(dòng)。 缺少的文檔是第一個(gè)提示。 通常是一團(tuán)糟的公共API。 但是,隨后也有將cglib部署到Maven Central的問題。 郵件列表的讀取就像垃圾郵件的存檔一樣。 并且釋放周期相當(dāng)不穩(wěn)定。 因此,您可能想看看javassist ,它是cglib的唯一真正的低級(jí)替代品。 Javassist捆綁了一個(gè)偽Java編譯器,該編譯器甚至無需了解Java字節(jié)代碼就可以創(chuàng)建非常驚人的字節(jié)代碼工具。 如果您想弄臟手,您可能還喜歡在cglib之上構(gòu)建的ASM 。 ASM附帶了有關(guān)庫和Java類文件及其字節(jié)碼的出色文檔。
翻譯自: https://www.javacodegeeks.com/2013/12/cglib-the-missing-manual.html
總結(jié)
以上是生活随笔為你收集整理的cglib:缺少的手册的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么关闭华为pay快捷键(怎么关闭华为p
- 下一篇: cvvhdf参数设置(如何设置cv参数)