動態代理之Cglib淺析


什么是Cglib

Cglib是一個強大的,高性能,高質量的代碼生成類庫。它可以在運行期擴展JAVA類與實現JAVA接口。其底層實現是通過ASM字節碼處理框架來轉換字節碼並生成新的類。大部分功能實際上是ASM所提供的,Cglib只是封裝了ASM,簡化了ASM操作,實現了運行期生成新的class。

Cglib的原理

運行時動態的生成一個被代理類的子類(通過ASM字節碼處理框架實現),子類重寫了被代理類中所有非final的方法。在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢植入橫切邏輯。

Cglib優缺點

優點:JDK動態代理要求被代理的類必須實現接口,當需要代理的類沒有實現接口時Cglib代理是一個很好的選擇。另一個優點是Cglib動態代理比使用java反射的JDK動態代理要快(Cglib的FastClass機制,解析參考http://www.cnblogs.com/cruze/p/3865180.html#lable3

缺點:對於被代理類中的final方法,無法進行代理,因為子類中無法重寫final函數

Cglib類庫

net.sf.cglib.core:底層字節碼處理類,他們大部分與ASM有關系。

net.sf.cglib.transform:編譯期或運行期類和類文件的轉換

net.sf.cglib.proxy:實現創建代理和方法攔截器的類

net.sf.cglib.reflect:實現快速反射和C#風格代理的類

net.sf.cglib.util:集合排序等工具類

net.sf.cglib.beans:JavaBean相關的工具類

最主要的一個接口CallBack接口,即回調接口,下面的攔截器、過濾器、延遲加載都繼承於它,結構圖如下:

 

Cglib的攔截器和過濾器

攔截器:實現MethodInterceptor接口的類,在intercept方法中實現對代理目標類的方法攔截。但同時Cglib為簡化和提高性能提供了一些專門的回調類型如FixedValue(可以在實現的方法loadObject中指定返回固定值,而不調用目標類函數)、NoOp(把對回調方法的調用直接委派到這個方法的父類,即不進行攔截)

過濾器:實現CallbackFilter接口的類,通過accept方法返回一個下標值,用於指定調用哪個攔截器進行攔截處理

/**
 * @author longe
 *    被代理類
 */
public class TDao {
    public void create() {
        System.out.println("create() is running !");
    }

    public void query() {
        System.out.println("query() is running !");
    }

    public void update() {
        System.out.println("update() is running !");
    }

    public void delete() {
        System.out.println("delete() is running !");
    }
}

/**
 * @author longe
 *    過濾器
 */
public class TDaoCglibFilter implements CallbackFilter {

    public final static int NO_RESTRICTION = 0;
    public final static int RESTRICTION_CREATE = 1;
    
    
    /* (non-Javadoc)
     *    根據調用的方法返回使用的callbacks下標
     */
    @Override
    public int accept(Method method) {
        if(method.getName().startsWith("create")){
            return RESTRICTION_CREATE;
        }
        return NO_RESTRICTION;
    }
}

/**
 * @author longe
 * 攔截器
 */
public class TDaoCglibProxy implements MethodInterceptor {

    private String name;
    private Object objT;
    
    public TDaoCglibProxy(String name) {
        this.name = name;
    }
    public TDaoCglibProxy(Object obj,String name) {
        this.name = name;
        this.objT = obj;
    }
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        if(!name.equals("張三")){
            System.out.println("沒有權限!");
            return null;
        }
//        return method.invoke(objT, args);    //通過反射進行調用
        return proxy.invokeSuper(obj, args);    //使用Cglib代理調用
    }


    public static void main(String[] args) {
        Enhancer en = new Enhancer();
        en.setSuperclass(TDao.class);
        TDaoCglibProxy callBack = new TDaoCglibProxy("張三1");
//        指定兩個callback,通過callBackFilter返回的下標值控制調用使用那一個callback處理
        en.setCallbacks(new Callback[] { NoOp.INSTANCE, callBack });
//        方法過濾器,根據過濾器返回不同的值回調對應下標的Callback方法
        en.setCallbackFilter(new TDaoCglibFilter());
        TDao dao = (TDao) en.create();
        dao.create();
        dao.query();
        dao.update();
        dao.delete();
    }
    
}
代碼示例
執行輸出如下:

沒有權限!
query() is running !
update() is running !
delete() is running !

可以看到只用create方法的調用被TDaoCglibProxy攔截器攔截了,從而判斷出沒有權限。而其他方法沒有被TDaoCglibProxy攔截器攔截,是因為在Filter過濾器中返回的索引為0,即指定的回調callback是NoOp,索引直接調用了父類的實現方法(即被代理類的實現)

Cglib的延遲加載

LazyLoader:當實際對象需要延遲加載時,可以使用LazyLoader回調。一旦實際對象被裝載,它將被每一個調用代理對象的方法使用(即實際對象被訪問時才調用此回調callback的loadObject方法)

Dispatcher:和LazyLoader回調具有相同的特點,區別是當代理方法被調用時,裝載對象的方法也總是要被調用

ProxyRefDispatcher:與Dispatcher一樣,只不過可以把代理對象作為參數進行傳遞

public class CglibLazyTest extends TestCase {

    public void testLazyBean() {
        TB tb = new TB();
        System.out.println("***************************************");
        System.out.println("cid's value: " + tb.getCid());
        System.out.println("***************************************");
        System.out.println("bean's username value: " + tb.getBean());
//        LazyLoader 這里將返回同一個對象
        System.out.println("bean's username value: " + tb.getBean());
        
        
        System.out.println("***************************************");
        System.out.println("bean2's username value: " + tb.getBean2());
//        這里將返回一個新的對象
        System.out.println("bean2's username value: " + tb.getBean2());
    }

    /**
     * @author longe
     *    通過實現LazyLoader接口延遲加載
     */
    class Lazy implements LazyLoader {
        @Override
        public Object loadObject() throws Exception {
            TestBean tb = new TestBean();
            System.out.println("lazy load ========= ");
            tb.setUserName("longe lazy");
            return tb;
        }

    }

    /**
     * @author longe
     *    通過實現Dispatcher接口延遲加載
     */
    class DispacherTest implements Dispatcher {
        @Override
        public Object loadObject() throws Exception {
            TestBean2 tb2 = new TestBean2();
            System.out.println("dispatcher load =========== ");
            tb2.setNo("longe dispatcher lazy");
            return tb2;
        }

    }
    /**
     * @author longe
     *    與Dispatcher一樣,只不過可以把代理對象作為參數進行傳遞
     */
    class ProxyRefDispacherTest implements ProxyRefDispatcher {
        @Override
        public Object loadObject(Object proxy) throws Exception {
            return null;
        }
    }

    class TB {
        private String cid;
        private TestBean bean;
        private TestBean2 bean2;
        LazyLoader lazy = new Lazy();
        Dispatcher dis = new DispacherTest();

        public TB() {
            cid = "no lazy";
            // Enhancer en = new Enhancer();
            // en.setSuperclass(TestBean.class);
            // en.setCallback(lazy);
            // bean = (TestBean) en.create(new Class[]{CglibLazyTest.class},new
            // Object[]{new CglibLazyTest()});
            bean = (TestBean) Enhancer.create(TestBean.class, lazy);
            bean2 = (TestBean2) Enhancer.create(TestBean2.class, dis);
            System.out.println("TB construct end...");
        }

        public TestBean getBean() {
            return bean;
        }

        public String getCid() {
            return cid;
        }

        public void setCid(String cid) {
            this.cid = cid;
        }

        public void setBean(TestBean bean) {
            this.bean = bean;
        }

        public TestBean2 getBean2() {
            return bean2;
        }

        public void setBean2(TestBean2 bean2) {
            this.bean2 = bean2;
        }
    }

}

class TestBean {
    public TestBean() {
        System.out.println("TestBean construct end...");
    }

    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

class TestBean2 {
    public TestBean2() {
        System.out.println("TestBean2 construct end...");
    }

    private String no;

    public String getNo() {
        return no;
    }

    public void setNo(String no) {
        this.no = no;
    }
}
代碼示例
執行輸出如下:

TestBean construct end...
TestBean2 construct end...
TB construct end...
***************************************
cid's value: no lazy
***************************************
TestBean construct end...
lazy load =========
bean's username value: practices.model.proxy.cglib.TestBean@77521871
bean's username value: practices.model.proxy.cglib.TestBean@77521871
***************************************
TestBean2 construct end...
dispatcher load ===========
bean2's username value: practices.model.proxy.cglib.TestBean2@2ec2dfea
TestBean2 construct end...
dispatcher load ===========
bean2's username value: practices.model.proxy.cglib.TestBean2@7bfa93a1

上面的示例中TestBean對象使用了LazyLoader回調,TestBean2對象使用了Dispatcher回調。

分析:

  1. 在TB的構造函數中創建了TestBean和TestBean2對象,此時這兩個對象分別被創建,但是是空對象。
  2. 當tb.getBean().getUserName()第一次訪問TestBean對象時,此時LazyLoader回調被調用,TestBean裝載並進行初始化賦值,最終返回裝載的新對象。
  3. 當tb.getBean().getUserName()第二次訪問TestBean對象時,TestBean已經被裝載過,此時LazyLoader回調不被調用。
  4. 當tb.getBean2().getNo()第一次訪問TestBean2對象時,此時Dispatcher回調被調用,TestBean2裝載並進行初始化賦值,最終返回裝載的新對象。
  5. 當tb.getBean2().getNo()第二次訪問TestBean2對象時,此時Dispatcher回調還是會被調用,TestBean2裝載並進行初始化賦值,最終返回一個新裝載的新對象

Cglib中的Mixin

net.sf.cglib.proxy.Mixin允許多個對象被綁定到一個單個的大對象。在代理中對方法的調用委托到下面對應的對象中。

通過指定代理類型和實際的代理對象參數即可直接創建代理。可以綁定多個,但需要注意類型數組與對象數組的一一對應。如:

public static void testCglibMixin() {
        System.out.println(MixinIfA.class.getName());
        Class[] ints = new Class[]{MixinIfA.class,MixinIfB.class};
        Object[] objs = new Object[]{new MixinAImpl(),new MixinBImpl()};
        Object obj = Mixin.create(ints,    objs);
        MixinIfA a = (MixinIfA) obj;
        MixinIfB b = (MixinIfB) obj;
        a.methodA();
        b.methodB();
    }

 public static Mixin create(Class[] interfaces, Object[] delegates)中第一個參數必須是接口類型,第二個參數是接口實現類對象實例

OK,到這Cglib代理了解的差不多了,從Cglib的動態代理知道它可以動態的生成java類,那么這里順帶貼上一段簡單的Cglib生成bean的代碼.(供后續復習用...)

/**
 * @author longe
 *    動態生成bean
 */
public class CglibBean {

    public Object object;
    public BeanMap beanMap;

    public CglibBean() {
    }

    public CglibBean(Map<String, Class> propertyMap) {
        this.object = generateBean(propertyMap);
        this.beanMap = BeanMap.create(this.object);
    }

    public void putValue(String property, Object value) {
        beanMap.put(property, value);
    }

    public Object getValue(String property) {
        return beanMap.get(property);
    }

    public Object getObject() {
        return this.object;
    }

    public Object generateBean(Map<String, Class> propertyMap) {
        BeanGenerator generator = new BeanGenerator();
        Set<String> keySet = propertyMap.keySet();
        for (String key : keySet) {
            generator.addProperty(key, propertyMap.get(key));
        }
        return generator.create();
    }

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Map<String, Class> propertyMap = new HashMap<>();
        propertyMap.put("id", Class.forName("java.lang.Integer"));
        propertyMap.put("name", Class.forName("java.lang.String"));
        propertyMap.put("address", Class.forName("java.lang.String"));
        CglibBean cglibBean = new CglibBean(propertyMap);
        cglibBean.putValue("id", 101);
        cglibBean.putValue("name", "longe");
        cglibBean.putValue("address", "beijing");
        System.out.println(cglibBean.getValue("id"));
        System.out.println(cglibBean.getValue("name"));
        System.out.println(cglibBean.getValue("address"));
        Object object = cglibBean.getObject();
        Method[] ms = object.getClass().getDeclaredMethods();
        for (Method method : ms) {
            System.out.println(method.getName());
            if (method.getName().startsWith("get")) {
                System.out.println("=====" + method.invoke(object, null));
            }
        }

    }
}
View Code

Cglib異常

java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
at net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:721)
at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)
at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at practices.model.proxy.cglib.CglibLazyTest.testLazyBean1(CglibLazyTest.java:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at junit.framework.TestCase.runTest(TestCase.java:176)
at junit.framework.TestCase.runBare(TestCase.java:141)
at junit.framework.TestResult$1.protect(TestResult.java:122)
at junit.framework.TestResult.runProtected(TestResult.java:142)
at junit.framework.TestResult.run(TestResult.java:125)
at junit.framework.TestCase.run(TestCase.java:129)
at junit.framework.TestSuite.runTest(TestSuite.java:252)
at junit.framework.TestSuite.run(TestSuite.java:247)
at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:86)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

原因:

  • 代理目標對象不能是內部類(因為內部類的創建依賴外部類),如果是內部類,cglib代理內部會獲取到一個有參構造函數(參數是外部類對象,如果實在需要代理一個內部類,可以通過傳遞構造參數實現)
  • Cglib代理默認創建一個缺省構造函數的目標對象,如果目標對象存在有參構造函數,Cglib進行代理時需要指定構造函數的參數,或者在目標對象上必須存在缺省構造函數,否則拋出異常(可以通過傳遞構造參數創建代理類)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM