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