当前位置: 首页 > 图灵资讯 > 技术篇> CGLIB详解(最详细)

CGLIB详解(最详细)

来源:图灵教育
时间:2023-06-27 15:09:36

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开始。

Enhancer

Enhancer可能是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 generator

cglib提供的操作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 Copier

cglib可以从一个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配置注入和注出的属性,只有在操作时才能确定处理。

BeanMap

JavavanMap类实现了 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 delegate

Methodelegate主要用于代理方法

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
  1. 多代理类似于方法代理,都委托代理方法的调用给被代理。使用前提是需要一个接口和一个类来实现这个接口
  2. 通过这种interface的继承关系,我们可以将接口上方法的调用分散到各种实现类别上。
  3. 多代理的缺点是接口只能包含一种方法。如果代理方法具有返回值,则调用代理返回值作为最后一种添加的代理方法返回值

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动态代理的区别
  1. Java动态代理只能代理接口,不能代理普通类别(因为所有生成的代理类别的父亲都是Proxy,Java继承机制不允许多次继承);CGLIB可以代理普通类;
  2. Java动态代理使用Java原始反射API操作,在生成类中更有效;CGLIB使用ASM框架直接操作字节码,在类别执行过程中更有效
  3. 3.

上一篇:

Spring Boot+MyBatis小白教程

下一篇:

JS 锚点