java之架構基礎-動態代理&cglib

本文核心主要參數動態代理和cglib;

在以前的文章中,有提及到動態代理,它要解決的就是,當我們的某些代碼前面或後面都需要一些處理的時候,如寫日誌、事務控制、做agent、自動化代碼跟蹤等,此時會給你帶來無限的方便,這是JVM級別的提供的一種代理機制,不過在這種機制下調用方法在JVM7出來前還沒有invokeDynamic的時候,調用的效率是很低的,此時方法調用都是通過method的invoke去實現。

其基本原理是基於實現JVM提供的一個:

InvocationHandler的接口,實現一個方法叫:public Object invoke(Object proxyed, Method method, Object[] args);

創建類的時候,通過實例化這個類(這個類就是實現InvocationHandler的類),再將實際要實現的類的class放進去,通過Proxy來實例化。

以下爲一段簡單動態代理的實現代碼(以下代碼放入一個文件:DynamicProxy.java):

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//定義了一個接口
interface Hello {
   public String getInfos1();
   public String getInfos2();
   public void setInfo(String infos1, String infos2);
   public void display();
}
//定義它的實現類
class HelloImplements implements Hello {

   private volatile String infos1;

   private volatile String infos2;

   public String getInfos1() {
      return infos1; 
   }

   public String getInfos2() {
     return infos2; 
   }

   public void setInfo(String infos1, String infos2) {
     this.infos1 = infos1;
     this.infos2 = infos2;
   }

   public void display() {
     System.out.println("\t\t" + infos1 + "\t" + infos2);
   }
}

定義AOP的Agent
class AOPFactory implements InvocationHandler {

   private Object proxyed;

   public AOPFactory(Object proxyed) {
     this.proxyed = proxyed;
   }

   public void printInfo(String info, Object... args) {
     System.out.println(info);
     if (args == null) {
       System.out.println("\t空值。");
     }else {
       for(Object obj : args) {
         System.out.println(obj);
       }
    }
}

public Object invoke(Object proxyed, Method method, Object[] args)  throws IllegalArgumentException, IllegalAccessException,
   InvocationTargetException {
     System.out.println("\n\n====>調用方法名:" + method.getName());
     Class<?>[] variables = method.getParameterTypes();
     for(Class<?>typevariables: variables) {
        System.out.println("=============>" + typevariables.getName());
     }
     printInfo("傳入的參數爲:", args);
     Object result = method.invoke(this.proxyed, args);
     printInfo("返回的參數爲:", result);
     printInfo("返回值類型爲:", method.getReturnType());
     return result;
  }
}
//測試調用類
public class DynamicProxy {

  public static Object getBean(String className) throws InstantiationException, IllegalAccessException,
     ClassNotFoundException {
      Object obj = Class.forName(className).newInstance();
      InvocationHandler handler = new AOPFactory(obj);
      return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
         .getClass().getInterfaces(), handler);
  }

  @SuppressWarnings("unchecked")
  public static void main(String[] args) {
    try {
        Hello hello = (Hello) getBean("dynamic.HelloImplements");
        hello.setInfo("xieyu1", "xieyu2");
        hello.getInfos1();
        hello.getInfos2();
        hello.display();
     } catch (Exception e) {
        e.printStackTrace();
     }
  }
}



OK,可以看看輸出結果,此時的輸出結果不僅僅有自己的Hello實現類的中打印結果,還有proxy代理中的結果,它可以捕獲方法信息和入參數,也可以捕獲返回結果,也可以操作方法,所以這種非侵入式編程本身就是侵入式的,呵呵!


好了,你會發現都有接口,有些時候寫太多接口很煩,而且上面的調用性能的確不怎麼樣,除了JVM提供的動態代理,還有什麼辦法嗎?有的,org的asm包可以動態修改字節碼信息,也就是可以動態在內存中創建class類和修改class類信息;但是聽起來貌似很複雜,cglib爲我們包裝了對asm的操作,整個ASM包的操作非常小,但是代碼很精煉,很容易看懂。那麼cglib實現的時候,就是通過創建一個類的子類,然後在調用時,子類方法肯定覆蓋父類方法,然後子類在完成相關動作後,進行super的回調;


我們來看個例子(首先下載asm包,和cglib包,各個版本不同而不同,我使用的是asm-all-3.1.jar和cglib-2.2.jar):

下面的程序只需創建文件:CglibIntereceptor.java 

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

//創建一個類,用來做測試
class TestClass {
   public void doSome() {
     System.out.println("====>咿呀咿呀喂");
   }
}
public class CglibIntereceptor {

   static class MethodInterceptorImpl implements MethodInterceptor {
     public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println(method);
        proxy.invokeSuper(obj, args);
        return null;
     }
}


  public static void main(String[] args) {
     Enhancer enhancer = new Enhancer();
     enhancer.setSuperclass(TestClass.class);
     enhancer.setCallback( new MethodInterceptorImpl() );
     TestClass my = (TestClass)enhancer.create();
     my.doSome();
  }
}


看看打印結果:

public void dynamic.TestClass.doSome()
====>咿呀咿呀喂


//注意看黑色粗體標識出來的代碼,首先要實現MethodInterceptor,然後實現方法intercept,內部使用invokeSuper來調用父類;下面的實例都是通過Enhancer 來完成的;細節的後續我們繼續探討,現在就知道這樣可以使用,而spring的真正實現也是類似於此,只是spring對於cglib的使用做了其他的包裝而已;大家可以去看看spring對事務管理器的源碼即可瞭解真相。


下面問題來了,我們有些時候對某些方法不想去AOP,因爲我認爲只有需要包裝的纔去包裝,就像事務管理器中切入的時候,我們一般會配置一個模式匹配,哪些類和那些方法才需要做AOP;那麼cglib怎麼實現的,它提供了一個CallbackFilter來實現這個機制。OK,我們來看一個CallbackFilter的實例:

以下代碼創建文件:CglibCallBackFilter.java

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;

class CallBackFilterTest {

  public void doOne() {
      System.out.println("====>1");
  }

  public void doTwo() {
      System.out.println("====>2");
  }
}

public class CglibCallBackFilter {

   static class MethodInterceptorImpl implements MethodInterceptor {
      public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
           System.out.println(method);
           return proxy.invokeSuper(obj, args);
     }
   }

    static class CallbackFilterImpl implements CallbackFilter {
           public int accept(Method method) {//返回1代表不會進行intercept的調用
           return ("doTwo".equals(method.getName())) ? 1 : 0;
       }
    }

 public static void main(String[] args) {
     Callback[] callbacks =
          new Callback[] { new MethodInterceptorImpl(),  NoOp.INSTANCE };
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(CallBackFilterTest.class);
      enhancer.setCallbacks( callbacks );
      enhancer.setCallbackFilter( new CallbackFilterImpl());
      CallBackFilterTest callBackFilterTest = (CallBackFilterTest)enhancer.create();
      callBackFilterTest.doOne();
      callBackFilterTest.doTwo();
  }
}


看下打印結果:

public void dynamic.CallBackFilterTest.doOne()
====>1
====>2

可以看到只有方法1打印出了方法名,方法2沒有,也就是方法2調用時沒有調用:intercept來做AOP操作,而是直接調用的;可以看出,他上層有一個默認值,而callbacks裏面設置了NoOp.INSTANCE,就代表了不做任何操作的一個實例,你應該懂了吧,就是當不做AOP的時候調用那種實例來運行,當需要AOP的時候調用那個實例來運行;怎麼對應上的,它自己不知道,accept返回的是一個數組的下標,callbacks是一個數組,那麼你猜猜是不是數組的下標呢,你自己換下位置就知道了,呵呵,是的,沒錯就是數組下標,不相信可以翻翻他的源碼就知道了。


其實你可以看出cglib就是在修改字節碼,貌似很方面,spring、Hibernate等也大量使用它,但是並不代表你可以大量使用它,尤其是在寫業務代碼的時候,只有寫框架纔可以適當考慮使用這些東西,spring的反射等相關一般都是初始化決定的,一般都是單例的,前面談及到JVM時,很多JVM優化原則都是基於VM的內存結構不會發生變化,如果發生了變化,那麼優化就會存在很多的問題了,其次無限制使用這個東西可能會使得VM的Perm Gen內存溢出。


最後我們看個實際應用中沒啥用途,但是cglib實現的一些東西,java在基於接口、抽象類的情況下,實現了很多特殊的機制,而cglib可以將兩個根本不想管的接口和類合併到一起來操作,這也是字節碼的一個功勞,呵呵,它的原理就是在接口下實現了子類,並把其他兩個作爲回調的方法,即可實現,但是實際應用中這種用法很詭異,cglib中是使用:Mixin來創建,而並非Enhancer了。例子如下:

創建文件:CglibMixin.java

import net.sf.cglib.proxy.Mixin;


interface Interface1 {
   public void doInterface1();
}
interface Interface2 {
   public void doInterface2();
}
class ImpletmentClass1 implements Interface1 {
   public void doInterface1() {
      System.out.println("===========>方法1");
   }
}
class ImpletmentClass2 implements Interface2 {
   public void doInterface2() {
      System.out.println("===========>方法2");
   }
}


public class CglibMixin {


   public static void main(String []args) {
       Class<?>[] interfaces =
             new Class[] { Interface1.class, Interface2.class };
       Object[] implementObjs =
             new Object[] { new ImpletmentClass1(), new ImpletmentClass2()};
       Object obj = Mixin.create(interfaces,implementObjs);
       Interface1 interface1 = (Interface1)obj;
       Interface2 interface2 = (Interface2)obj;
       interface1.doInterface1();
       interface2.doInterface2();
   }
}


結果就不用打印了,上面有描述,主要是兩個接口、兩個實例,最終用一個對象完成了,傳遞過程中只有一個,比起傳統意義上的多態更加具有多態的效果,呵呵,不過還是建議少用。

本文只是簡單闡述框架級別動態代理和cglib的實現,後續會深入探討一些cglib的實現細節和功能,以及如何在框架中抽象出模型出來。

發佈了68 篇原創文章 · 獲贊 364 · 訪問量 75萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章