1. 程式人生 > >CGLIB原理及實現機制

CGLIB原理及實現機制

什麼是CGLIB

CGLIB(Code Generator Library)是一個強大的、高效能的程式碼生成庫。其被廣泛應用於AOP框架(Spring、dynaop)中,用以提供方法攔截操作。Hibernate作為一個比較受歡迎的ORM框架,同樣使用CGLIB來代理單端(多對一和一對一)關聯(延遲提取集合使用的另一種機制)。

為什麼使用CGLIB

CGLIB代理主要通過對位元組碼的操作,為物件引入間接級別,以控制物件的訪問。我們知道Java中有一個動態代理也是做這個事情的,那我們為什麼不直接使用Java動態代理,而要使用CGLIB呢?答案是CGLIB相比於JDK動態代理更加強大,JDK動態代理雖然簡單易用,但是其有一個致命缺陷是,只能對介面進行代理。如果要代理的類為一個普通類、沒有介面,那麼Java動態代理就沒法使用了。關於Java動態代理,可以參者這裡

Java動態代理分析

CGLIB組成結構

image

CGLIB底層使用了ASM(一個短小精悍的位元組碼操作框架)來操作位元組碼生成新的類。除了CGLIB庫外,指令碼語言(如Groovy何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 world
after method run...

在上面的程式中,我們引入了Enhancer和MethodInterceptor,可能有些讀者還不太瞭解。別急,我們後面將會一一進行介紹。就目前而言,一個使用CGLIB的小demo就完成了

常用的API

目前網路上對CGLIB的介紹資料比較少,造成對cglib的學習困難。這裡我將對cglib中的常用類進行一個介紹。為了避免解釋的不清楚,我將為每個類都配有一個demo,用來做進一步的說明。首先就從Enhancer開始吧。

Enhancer

Enhancer可能是CGLIB中最常用的一個類,和Java1.3動態代理中引入的Proxy類差不多(如果對Proxy不懂,可以參考這裡)。和Proxy不同的是,Enhancer既能夠代理普通的class,也能夠代理介面。Enhancer建立一個被代理物件的子類並且攔截所有的方法呼叫(包括從Object中繼承的toString和hashCode方法)。Enhancer不能夠攔截final方法,例如Object.getClass()方法,這是由於Java final方法語義決定的。基於同樣的道理,Enhancer也不能對fianl類進行代理操作。這也是Hibernate為什麼不能持久化final class的原因。

public class SampleClass {
    public String test(String input){
        return "hello world";
    }
}

下面我們將以這個類作為主要的測試類,來測試呼叫各種方法

@Test
public 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 cglib
Hello cglib
class com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7

java.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$$EnhancerByCGLIB$$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的例子在前面的hello world中已經介紹過了,這裡就不浪費時間了。

有些時候我們可能只想對特定的方法進行攔截,對其他的方法直接放行,不做任何操作,使用Enhancer處理這種需求同樣很簡單,只需要一個CallbackFilter即可:

@Test
public 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允許建立一個原來物件的包裝類,這個包裝類是不可變的,任何改變底層物件的包裝類操作都會丟擲IllegalStateException。但是我們可以通過直接操作底層物件來改變包裝類物件。這有點類似於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。

@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");
    Assert.assertEquals("Hello cglib",getter.invoke(myBean));
}

在上面的程式碼中,我們使用cglib動態的建立了一個和SampleBean相同的Bean物件,包含一個屬性value以及getter、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;
    }
}

@Test
public 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兩個方法,允許自定義處理屬性

@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");
    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建立物件,一般將其宣告為static的 
2. 應用場景:針對特定屬性的get,set操作,一般適用通過xml配置注入和注出的屬性,執行時才確定處理的Source,Target類,只需要關注屬性名即可。

BeanMap

BeanMap類實現了Java Map,將一個bean物件中的所有屬性轉換為一個String-to-Obejct的Java Map

@Test
public 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);
}

我們首先構造一個滿足條件的介面,然後進行測試

@Test
public 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[]{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類比較尷尬,因為他要求Minix的類(例如MixinInterface)實現一些介面。既然被Minix的類已經實現了相應的介面,那麼我就直接可以通過純Java的方式實現,沒有必要使用Minix類。

String switcher

用來模擬一個String到int型別的Map型別。如果在Java7以後的版本中,類似一個switch語句。

@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"));
}

Interface Maker

正如名字所言,Interface Maker用來建立一個新的Interface

@Test
public 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

MethodDelegate主要用來對方法進行代理

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 cglib", delegate.getValueFromDelegate());
}

關於Method.create的引數說明: 
1. 第二個引數為即將被代理的方法 
2. 第一個引數必須是一個無引數構造的bean。因此MethodDelegate.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;
    }
}

@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());
}

Constructor delegate

為了對建構函式進行代理,我們需要一個介面,這個介面只含有一個Object newInstance(…)方法,用來呼叫相應的建構函式

interface SampleBeanConstructorDelegate{
    Object newInstance(String value);
}

/**
 * 對建構函式進行代理
 * @throws Exception
 */
@Test
public 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(並行排序器)

能夠對多個數組同時進行排序,目前實現的演算法有歸併排序和快速排序

@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;
        }
    }
}

FastClass

顧明思義,FastClass就是對Class物件進行特定的處理,比如通過陣列儲存method引用,因此FastClass引出了一個index下標的新概念,比如getIndex(String name, Class[] parameterTypes)就是以前的獲取method的方法。通過陣列儲存method,constructor等class資訊,從而將原先的反射呼叫,轉化為class.index的直接呼叫,從而體現所謂的FastClass。

@Test
public 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框架直接對位元組碼進行操作,在類的執行過程中比較高效