Cglib及其基本使用

原文地址:https://www.cnblogs.com/xrq730/p/6661692.html

一、Cglib是什麼

Cglib是一個強大的、高性能的代碼生成包,它廣泛被許多AOP框架使用,爲他們提供方法的攔截。下圖是我網上找到的一張Cglib與一些框架和語言的關係:

對此圖總結一下:

  • 最底層的是字節碼Bytecode,字節碼是Java爲了保證“一次編譯、到處運行”而產生的一種虛擬指令格式,例如iload_0、iconst_1、if_icmpne、dup等
  • 位於字節碼之上的是ASM(assembly  意爲彙編,裝配),這是一種直接操作字節碼的框架,應用ASM需要對Java字節碼、Class結構比較熟悉
  • 位於ASM之上的是CGLIB、Groovy、BeanShell,後兩種並不是Java體系中的內容而是腳本語言,它們通過ASM框架生成字節碼變相執行Java代碼,這說明在JVM中執行程序並不一定非要寫Java代碼----只要你能生成Java字節碼,JVM並不關心字節碼的來源,當然通過Java代碼生成的JVM字節碼是通過編譯器直接生成的,算是最“正統”的JVM字節碼
  • 位於CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP這些框架了,這一層大家都比較熟悉
  • 最上層的是Applications,即具體應用,一般都是一個Web項目或者本地跑一個程序 

二、使用Cglib代碼對類做代理

詳細請參照代理模式詳解之Cglib動態代理:https://mp.csdn.net/postedit/88715234

下面演示一下Cglib代碼示例----對類做代理。首先定義一個Dao類,裏面有一個select()方法和一個update()方法:

三、使用Cglib定義不同的攔截策略

再擴展一點點,比方說在AOP中我們經常碰到的一種複雜場景是:我們想對類A的B方法使用一種攔截策略、類A的C方法使用另外一種攔截策略

如果我們想對同一個類的兩個方法使用不同的攔截策略,例子如下:

創建一個Dao代理,實現MethodInterceptor接口,目標是在update()方法與select()方法調用前後輸出兩句話:

public class Dao {
    
    public void update() {
        System.out.println("PeopleDao.update()");
    }
    
    public void select() {
        System.out.println("PeopleDao.select()");
    }
}

 

intercept方法的參數名並不是原生的參數名,我做了自己的調整,幾個參數的含義爲:

  • Object表示要進行增強的對象
  • Method表示攔截的方法
  • Object[]數組表示參數列表,基本數據類型需要傳入其包裝類型,如int-->Integer、long-Long、double-->Double
  • MethodProxy表示對方法的代理,invokeSuper方法表示對被代理對象方法的調用
public class DaoProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        System.out.println("Before Method Invoke");
        proxy.invokeSuper(object, objects);
        System.out.println("After Method Invoke");
        
        return object;
    }
    
}

再定義一個新的proxy類

public class DaoAnotherProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        
        System.out.println("StartTime=[" + System.currentTimeMillis() + "]");
        method.invoke(object, objects);
        System.out.println("EndTime=[" + System.currentTimeMillis() + "]");
        return object;
    }
    
}

方法調用前後輸出一下開始時間與結束時間。爲了實現我們的需求,實現一下CallbackFilter:

public class DaoFilter implements CallbackFilter {

    @Override
    public int accept(Method method) {
        if ("select".equals(method.getName())) {
            return 0;
        }
        return 1;
    }
    
}

返回的數值表示順序,結合下面的代碼解釋,測試代碼: 

public class CglibTest {

    @Test
    public void testCglib() {
        DaoProxy daoProxy = new DaoProxy();
        DaoAnotherProxy daoAnotherProxy = new DaoAnotherProxy();
        
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);
        enhancer.setCallbacks(new Callback[]{daoProxy, daoAnotherProxy, NoOp.INSTANCE});
        enhancer.setCallbackFilter(new DaoFilter());
        
        Dao dao = (Dao)enhancer.create();
        dao.update();
        dao.select();
    }
    
}

意思是CallbackFilter的accept方法返回的數值表示的是順序,順序和setCallbacks裏面Proxy的順序是一致的。再解釋清楚一點,Callback數組中有三個callback,那麼:

  • 方法名爲"select"的方法返回的順序爲0,即使用Callback數組中的0位callback,即DaoProxy
  • 方法名不爲"select"的方法返回的順序爲1,即使用Callback數組中的1位callback,即DaoAnotherProxy

因此,方法的執行結果爲:

 

符合我們的預期,因爲update()方法不是方法名爲"select"的方法,因此返回1,返回1使用DaoAnotherProxy,即打印時間;select()方法是方法名爲"select"的方法,因此返回0,返回0使用DaoProxy,即方法調用前後輸出兩句話。

這裏要額外提一下,Callback數組中我特意定義了一個NoOp.INSTANCE,這表示一個空Callback,即如果不想對某個方法進行攔截,可以在DaoFilter中返回2,具體效果可以自己嘗試一下。

四、構造函數不攔截方法

如果Update()方法與select()方法在構造函數中被調用,那麼也是會對這兩個方法進行相應的攔截的,現在我想要的是構造函數中調用的方法不會被攔截,那麼應該如何做?先改一下Dao代碼,加一個構造方法Dao(),調用一下update()方法:

public class Dao {
    
    public Dao() {
        update();
    }
    
    public void update() {
        System.out.println("PeopleDao.update()");
    }
    
    public void select() {
        System.out.println("PeopleDao.select()");
    }
}

如果想要在構造函數中調用update()方法時,不攔截的話,Enhancer中有一個setInterceptDuringConstruction(boolean interceptDuringConstruction)方法設置爲false即可,默認爲true,即構造函數中調用方法也是會攔截的。那麼測試方法這麼寫:

public class CglibTest {

    @Test
    public void testCglib() {
        DaoProxy daoProxy = new DaoProxy();
        DaoAnotherProxy daoAnotherProxy = new DaoAnotherProxy();
        
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);
        enhancer.setCallbacks(new Callback[]{daoProxy, daoAnotherProxy, NoOp.INSTANCE});
        enhancer.setCallbackFilter(new DaoFilter());
        enhancer.setInterceptDuringConstruction(false);
        
        Dao dao = (Dao)enhancer.create();
        dao.update();
        dao.select();
    }
    
}

 運行結果爲:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章