字节码检测库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称为特征)中了解了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
可以将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