CGLIB是什么?
CGLIB是一个强大而高性能的代码生成库。广泛应用于AOP框架(Spring、dynaop)用于提供拦截操作的方法。作为一个流行的ORM框架,Hibernate还使用CGLIB代理单端(多对一和一对一)关联(另一种延迟提取和集合使用的机制)。作为一个开源项目,CGLIB的代码托管在github,地址为:https://github.com/cglib/cglib
为什么使用CGLIBIB通过操作字节码,CGLIB代理主要将间接级别引入对象,以控制对象的访问。我们知道Java中有一个动态代理也这样做,那么为什么不直接使用Java动态代理,而是使用CGLIB呢?答案是CGLIB比JDK动态代理更强大。虽然JDK动态代理简单易用,但它的一个致命缺陷是只能代理接口。如果要代理的类别是普通类,没有接口,Java动态代理就不能使用。Java动态代理可以参与Java动态代理的分析
CGLIB组成结构CGLIB底部使用ASM(一个短而精确的字节码操作框架)来操作字节码生成一个新的类别。除了CGLIB库,脚本语言(如Grovy何BeanShell)也使用ASM生成字节码。ASM使用类似于SAX的分析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式有足够的了解
例子说了这么多,你可能还是不知道CGLIB是干什么用的。下面我们将用一个简单的例子来演示如何用CGLIB拦截一种方法。首先,我们需要在项目的POM文件中引入CGLIB的dependency,我们在这里使用2.2.2版本
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version></dependency>
下载依赖包后,我们就可以工作了。按照国际惯例,写hello world
public class SampleClass { public void test(){ System.out.println("hello world"); } public static void main(String[] args) { 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 { System.out.println("before method run..."); Object result = proxy.invokeSuper(obj, args); System.out.println("after method run..."); return result; } }); SampleClass sample = (SampleClass) enhancer.create(); sample.test(); }}
在mian函数中,我们通过一个enhancer和一个methodinterceptor拦截方法,运行程序后输出为:
before method run...hello worldafter method run...
在以上程序中,我们介绍了Enhancer和Methodinterceptor,有些读者可能不太了解。别担心,我们稍后会一一介绍。目前,使用CGLIB的小demo已经完成
常用的API目前,CGLIB在互联网上的介绍较少,导致学习CGLIB的困难。在这里,我将介绍CGLIB中常用的类别。为了避免不清楚的解释,我将为每个类别提供进一步的解释。让我们从Enhancer开始。
EnhancerEnhancer可能是CGLIB中最常用的一类,类似于Java1.3动态代理中引入的Proxy(如果不了解Proxy,可以参考这里)。与Proxy不同的是,Enhancer不仅可以代表普通的class,还可以代表接口。Enhancer创建了代理对象的子类,并拦截了所有方法(包括从Object中继承的tostring和hashcode方法)。Enhancer不能拦截final方法,例如objectter.getClass()方法,这是因为Java 由final方法语义决定。同理,Enhancer也不能代理fianl类。这就是为什么Hibernate不能持久的finall class的原因。
public class SampleClass { public String test(String input){ return "hello world"; }}
接下来,我们将以此类为主要测试类,测试和调用各种方法
@Testpublic void testFixedValue(){ 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(); System.out.println(proxy.test(null)); //截获test,输出Hello cglib System.out.println(proxy.toString()); System.out.println(proxy.getClass()); System.out.println(proxy.hashCode());}
程序输出为:
Hello cglibHello cglibclass com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number at com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7.hashCode(<generated>) ...
在上述代码中,FixedValue用于返回所有拦截方法的相同值。从输出中,我们可以看到Enhancer对非final方法的test()、toString()、hashCode()拦截,未拦截getClass。因为hashcode()方法需要返回一个number,但我们返回一个string,这解释了为什么在上述程序中抛出异常。
Enhancer.setsuperclass用于设置父类型,从tostring方法可以看出,使用CGLIB生成的类是代理类的子类,形如:SampleClass<script type="math/tex" id="MathJax-Element-1"></script>EnhancerByCGLIB<script type="math/tex" id="MathJax-Element-2"></script>e3ea9b7
Enhancer.create(Object…)该方法用于创建增强对象,它提供了许多不同参数的方法来匹配增强类的不同结构方法。(虽然类的结构放法只是Java字节码层面的函数,但Enhancer无法操作。Enhancer也不能操作static或final类)。也可以先用Enhancer.createClass()创建字节码(.class),然后用字节码动态生成增强对象。
您可以使用Invocationhandler(如果您不了解Invocationhandler,请参考此处)作为回调,使用invoke方法替换直接访问方法,但您必须注意死循环。因为invoke中调用的任何原始代理方法都将被重新代理到invoke方法中。
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(); Assert.assertEquals("hello cglib", proxy.test(null)); Assert.assertNotEquals("Hello cglib", proxy.toString());}
为了避免这种死循环,我们可以使用Methodinterceptor、Methodinterceptor的例子 world已经介绍过了,这里就不浪费时间了。
有时候我们可能只是想拦截特定的方法,直接放行其他方法,不做任何操作。用Enhancer处理这个需求也很简单,只需要一个CallbackFilter:
@Testpublic 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; } } }; enhancer.setSuperclass(SampleClass.class); enhancer.setCallbackFilter(callbackHelper); enhancer.setCallbacks(callbackHelper.getCallbacks()); SampleClass proxy = (SampleClass) enhancer.create(); Assert.assertEquals("Hello cglib", proxy.test(null)); Assert.assertNotEquals("Hello cglib",proxy.toString()); System.out.println(proxy.hashCode());}
ImmutableBean你可以通过名字知道不可改变的Bean。ImmutableBean允许创建原始对象的包装类别,这是不可改变的。任何改变底层对象的包装操作都将抛出IllegalStatexception。但我们可以通过直接操作底层对象来改变包装对象。这有点类似于Guava中不可改变的视图
为了测试Immutablebean,需要在这里引入另一个bean
public class SampleBean { private String value; public SampleBean() { } public SampleBean(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; }}
然后编写测试类如下:
@Test(expected = IllegalStateException.class)public void testImmutableBean() throws Exception{ SampleBean bean = new SampleBean(); bean.setValue("Hello world"); SampleBean immutableBean = (SampleBean) ImmutableBean.create(bean); //创建不可变类 Assert.assertEquals("Hello world",immutableBean.getValue()); bean.setValue("Hello world, again"); ///可以通过底层对象进行修改 Assert.assertEquals("Hello world, again", immutableBean.getValue()); immutableBean.setValue("Hello cglib"); ///直接修改throw exception}
Bean generatorcglib提供的操作bean的工具,可以在运行过程中动态创建bean。
@Testpublic 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"); Assert.assertEquals("Hello cglib",getter.invoke(myBean));}
在上述代码中,我们利用cglib动态创建了与SampleBean相同的Bean对象,包括属性value和geter、setter方法
Bean Copiercglib可以从一个bean复制到另一个bean,它还提供了一个转换器来操作bean的属性。
public class OtherSampleBean { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; }}@Testpublic void testBeanCopier() throws Exception{ BeanCopier copier = BeanCopier.create(SampleBean.class, OtherSampleBean.class, false);///设置为true,使用converter SampleBean myBean = new SampleBean(); myBean.setValue("Hello cglib"); OtherSampleBean otherBean = new OtherSampleBean(); copier.copy(myBean, otherBean, null); ///如果设置为true,则输入converter指示如何转换 assertEquals("Hello cglib", otherBean.getValue());}
BulkBean与beancopier相比,bulkbean将copy的动作分为两种方法:getpropertyvalues和setpropertyvalues,允许自定义处理属性
@Testpublic 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"); Object[] propertyValues = bulkBean.getPropertyValues(bean); 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());}
使用注意:1. 避免每次BulkBean.create创建对象,一般声明为static2. 应用场景:对于特定属性的get和set操作,source和target一般适用于xml配置注入和注出的属性,只有在操作时才能确定处理。
BeanMapJavavanMap类实现了 Map,将bean对象中的所有属性转换为String-to-ObejctJava Map
@Testpublic void testBeanMap() throws Exception{ BeanGenerator generator = new BeanGenerator(); generator.addProperty("username",String.class); generator.addProperty("password",String.class); Object bean = generator.create(); Method setUserName = bean.getClass().getMethod("setUsername", String.class); Method setPassword = bean.getClass().getMethod("setPassword", String.class); setUserName.invoke(bean, "admin"); setPassword.invoke(bean,"password"); BeanMap map = BeanMap.create(bean); Assert.assertEquals("admin", map.get("username")); Assert.assertEquals("password", map.get("password"));}
我们用Beangenerator生成了一个具有两个属性的Java Bean,赋值操作后,通过获取值验证生成BeanMap对象
keyFactory用于动态生成接口的keyFactory类实例需要只包含一个newinstance方法,返回一个object。keyFactory动态生成object构建的实例.equals和Object.hashCode方法可以确保相同参数构造的实例为单例。
public interface SampleKeyFactory { Object newInstance(String first, int second);}
我们首先建立一个符合条件的接口,然后进行测试
@Testpublic void testKeyFactory() throws Exception{ SampleKeyFactory keyFactory = (SampleKeyFactory) KeyFactory.create(SampleKeyFactory.class); Object key = keyFactory.newInstance("foo", 42); Object key1 = keyFactory.newInstance("foo", 42); Assert.assertEquals(key,key1);///测试参数相同,结果是否相等}
Mixin(混合)如果这些对象必须是接口的实现,Mixin可以让我们将多个对象集成到一个对象中。也许这是模糊的。以代码为例:
public class MixinInterfaceTest { interface Interface1{ String first(); } interface Interface2{ String second(); } class Class1 implements Interface1{ @Override public String first() { return "first"; } } class Class2 implements Interface2{ @Override public String second() { return "second"; } } interface MixinInterface extends Interface1, Interface2{ } @Test public void testMixin() throws Exception{ Mixin mixin = Mixin.create(new Class[]{Interface.class, Interface2.class, MixinInterface.class}, new Object[]{new Class1(),new Class2()}; MixinInterface mixinDelegate = (MixinInterface) mixin; assertEquals("first", mixinDelegate.first()); assertEquals("second", mixinDelegate.second()); }}
Mixin类比较尴尬,因为他要求Minix类(比如Mixinininterface)实现一些接口。由于Minix类已经实现了相应的接口,我可以直接通过纯Java实现,没有必要使用Minix类。
String switcher用于模拟String到int类型的Map类型。如果在Java7后的版本中,类似于switch语句。
@Testpublic 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"));}
Interface Maker正如名字所说,Interface Maker用于创建一个新的Interface
@Testpublic void testInterfaceMarker() 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());}
上述Interface Maker创建的界面只有一种方法,签名为double foo(int)。Interface 与上面介绍的其他类型不同,Maker依赖于ASM中的Type类型。由于界面仅用于编译期间的类型检查,因此在操作应用程序中动态创建界面没有效果。但是InterfaceMaker可以用来自动生成代码,为以后的开发做准备。
Method delegateMethodelegate主要用于代理方法
interface BeanDelegate{ String getValueFromDelegate();}@Testpublic void testMethodDelegate() throws Exception{ SampleBean bean = new SampleBean(); bean.setValue("Hello cglib"); BeanDelegate delegate = (BeanDelegate) MethodDelegate.create(bean,"getValue", BeanDelegate.class); assertEquals("Hello cglib", delegate.getValueFromDelegate());}
关于Method.create参数说明:1. 第二个参数是即将被代理的方法2. 第一个参数必须是无参数结构的bean。因此,Methodelegategategater.create并不像你想象的那么有用。3. 第三个参数只包含一个方法界面。当该接口中的方法被调用时,第一个参数指向bean的第二个参数方法将被调用
缺点:1. 为每个代理类别创建一个新的类别,这可能会占用大量的永久堆内存2. 你不能代表需要参数的方法3. 如果您在界面中定义的方法需要参数,代理将不工作或抛出异常;如果您的界面方法需要其他返回类型,则将抛出Illegalargumentexception
MulticastDelegate- 多代理类似于方法代理,都委托代理方法的调用给被代理。使用前提是需要一个接口和一个类来实现这个接口
- 通过这种interface的继承关系,我们可以将接口上方法的调用分散到各种实现类别上。
- 多代理的缺点是接口只能包含一种方法。如果代理方法具有返回值,则调用代理返回值作为最后一种添加的代理方法返回值
public interface DelegatationProvider { void setValue(String value);}public class SimpleMulticastBean implements DelegatationProvider { private String value; @Override public void setValue(String value) { this.value = value; } public String getValue() { return value; }}@Testpublic 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());}
Constructor delegate为了代理构造函数,我们需要一个只含有Objectt的界面 newInstance(..)调用相应的构造函数的方法
interface SampleBeanConstructorDelegate{ Object newInstance(String value);}/** * 代理构造函数 * @throws Exception */@Testpublic void testConstructorDelegate() throws Exception{ SampleBeanConstructorDelegate constructorDelegate = (SampleBeanConstructorDelegate) ConstructorDelegate.create( SampleBean.class, SampleBeanConstructorDelegate.class); SampleBean bean = (SampleBean) constructorDelegate.newInstance("Hello world"); assertTrue(SampleBean.class.isAssignableFrom(bean.getClass())); System.out.println(bean.getValue());}
Parallel Sorter(并行排序器)可以同时对多个数组进行排序。目前实现的算法包括合并排序和快速排序
@Testpublic 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; } }}
FastClass顾明思义,FastClass是对Class对象的具体处理,比如通过几组保存method引用,所以FastClass引出了index下标的新概念,比如getindex(String name, Class[] parameterTypes)这是以前获得method的方法。通过数组存储class信息,如method、constructor等,从而将原始反射调用到class.直接调用index,从而反映所谓的FastClass。
@Testpublic void testFastClass() throws Exception{ FastClass fastClass = FastClass.create(SampleBean.class); FastMethod fastMethod = fastClass.getMethod("getValue",new Class[0]); SampleBean bean = new SampleBean(); bean.setValue("Hello world"); assertEquals("Hello world",fastMethod.invoke(bean, new Object[0]));}
注意由于CGLIB的大部分类别都是直接操作Java字节码,因此生成的类别将在Java的永久堆中。如果动态代理操作过多,很容易导致永久填充,触发Outofmemory异常。
CGLIB和Java动态代理的区别- Java动态代理只能代理接口,不能代理普通类别(因为所有生成的代理类别的父亲都是Proxy,Java继承机制不允许多次继承);CGLIB可以代理普通类;
- Java动态代理使用Java原始反射API操作,在生成类中更有效;CGLIB使用ASM框架直接操作字节码,在类别执行过程中更有效
- 3.