agapple

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            13 Posts :: 1 Stories :: 1 Comments :: 0 Trackbacks

          背景

              前段時間在工作中,包括一些代碼閱讀過程中,spring aop經常性的會看到cglib中的相關內容,包括BeanCopier,BulkBean,Enancher等內容,以前雖大致知道一些內容,原理是通過bytecode,但沒具體深入代碼研究,只知其所用不知其所以然,所以就特地花了半天多的工作時間研究了CGLIB的相關源碼,同時結合看了下 spring Aop中對CGLIB的使用。

          cglib基本信息

          1. cglib的官方網站: http://cglib.sourceforge.net/
          2. cglib目前的最新版本應該是2.2,公司普遍使用的版本也是這個
          3. 官網的samples : http://cglib.sourceforge.net/xref/samples/

          cglib代碼包結構

          • core (核心代碼)
            • EmitUtils
            • ReflectUtils
            • KeyFactory
            • ClassEmitter/CodeEmitter
            • NamingPolicy/DefaultNamingPolicy
            • GeneratorStrategy/DefaultGeneratorStrategy
            • DebuggingClassWriter
            • ClassGenerator/AbstractClassGenerator
          • beans (bean操作類)
            • BeanCopier
            • BulkBean
            • BeanMap
            • ImmutableBean
            • BeanGenerator
          • reflect
            • FastClass
          • proxy
            • Enhancer
            • CallbackGenerator
            • Callback
              • MethodInterceptor , Dispatcher, LazyLoader , ProxyRefDispatcher , NoOp , FixedValue , InvocationHandler(提供和jdk proxy的功能)
            • CallbackFilter
          • util
            • StringSwitcher 
            • ParallelSorter 
          • transform 

          core核心代碼部分

          EmitUtils

          重要的工具類,主要封裝了一些操作bytecode的基本函數,比如生成一個null_constructor,添加類屬性add_property等

          ReflectUtils

          處理jdk reflect的工具類,比如獲取一個類所有的Method,獲取構造函數信息等。

          ClassEmitter/CodeEmitter

          對asm的classAdapter和MethodAdapter的實現,貫穿于cglib代碼的處理

          KeyFactory

          類庫中重要的唯一標識生成器,用于cglib做cache時做map key,比較底層的基礎類。
          例子:

          interface BulkBeanKey {
          public Object newInstance(String target, String[] getters, String[] setters, String[] types);
          }

          (BulkBeanKey)KeyFactory.create(BulkBeanKey.
          class).newInstance(targetClassName, getters, setters, typeClassNames);



          說明:

          • 每個Key接口,都必須提供newInstance方法,但具體的參數可以隨意定義,通過newInstance返回的為一個唯一標示,只有當傳入的所有參數的equals都返回true時,生成的key才是相同的,這就相當于多key的概念。

          NamingPolicy

          默認的實現類:DefaultNamingPolicy, 具體cglib動態生成類的命名控制。
          一般的命名規則:

          • 被代理class name + "$$" + 使用cglib處理的class name + "ByCGLIB" + "$$" + key的hashcode
          • 示例:FastSource$$FastClassByCGLIB$$e1a36bab.class

          GeneratorStrategy

          默認的實現類: DefaultGeneratorStrategy
          控制ClassGenerator生成class的byte數據,中間可插入自己的處理。注意這里依賴了:DebuggingClassWriter進行class generator的處理

          DebuggingClassWriter

          cglib封裝asm的處理類,用于生成class的byte流,通過GeneratorStrategy回調ClassGenerator.generateClass(DebuggingClassWriter),將自定義的class byte處理回調給具體的cglib上層操作類,比如由具體的BeanCopier去控制bytecode的生成。

          ClassGenerator

          其中一個抽象實現:AbstractClassGenerator。cglib代碼中核心的Class bytecode操作主體,包含了一些cache,調用NamingPolicy,GeneratorStrategy進行處理,可以說是一個最核心的調度者。

           

           

          對應的類圖:

           

          1. 外部的BeanCopier都包含了一Generator,繼承自AbstractClassGenerator,實現了generateClass(ClassVisitor v),Object firstInstance(Class type)方法。
          2. AbstractClassGenerator自身會根據Source進行cache,所以針對已經生成過的class,這里KeyFactory對應的值要相等,則會直接返回cache中的結果。所以BeanCopier每次create慢只是每次都需要new兩個對象,一個是KeyFactory.newInstance,另一個是firstInstance方法調用生成一個對象。

          反編譯tips

          大家都知道cglib是進行bytecode操作,會動態生成class,最快最直接的學習就是結合他生成的class,對照代碼進行學習,效果會好很多。

          system.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "指定輸出目錄"); 

           可參見 cores/DebuggingClassWriter代碼。說明:這樣cglib會將動態生成的每個class都輸出到文件中,然后我們可以通過decomp進行反編譯查看源碼。

           

          beans (相關操作類)

          BeanCopier

          簡單的示例代碼就不做介紹,相信大家都指導怎么用,這里主要介紹下Convert的使用。

          • 許多網友都做過BeanCopier,BeanUtils的測試,基本BeanCopier的性能是BeanUtils的10倍以上。,出了反射這一性能差異外,BeanUtils默認是開啟Converter功能,允許同名,不同類型的屬性進行拷貝,比如Date對象到String屬性。
          • 有興趣的同學可以去比較下PropertyUtils,默認不開啟Converter功能,發現性能是BeanUtils的2倍多。

          初始化例子:BeanCopier copier = BeanCopier.create(Source.class, Target.class, true);
          第三個參數useConverter,是否開啟Convert,默認BeanCopier只會做同名,同類型屬性的copier,否則就會報錯。

          
          
          public class BeanCopierTest {

              
          public static void main(String args[]) {
                  System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, 
          "/tmp/1");
                  BeanCopier copier 
          = BeanCopier.create(Source.class, Target.classtrue);
                  Source from 
          = new Source();
                  from.setValue(
          1);

                  Target to 
          = new Target();
                  Converter converter 
          = new BigIntConverter();
                  copier.copy(from, to, converter); 
          //使用converter類

                  System.out.println(to.getValue());
              }

          }


          class BigIntConverter implements net.sf.cglib.core.Converter {

              @Override
              
          public Object convert(Object value, Class target, Object context) {
                  System.out.println(value.getClass() 
          + " " + value); // from類中的value對象
                  System.out.println(target); // to類中的定義的參數對象
                  System.out.println(context.getClass() + " " + context); // String對象,具體的方法名
                  if (target.isAssignableFrom(BigInteger.class)) {
                      
          return new BigInteger(value.toString());
                  }
           else {
                      
          return value;
                  }

              }


          }

          ----
          反編譯后看的代碼:
          public class Target$$BeanCopierByCGLIB$$e1c34377 extends BeanCopier
          {
              
          public void copy(Object obj, Object obj1, Converter converter)
              
          {
                  Target target 
          = (Target)obj1;
                  Source source 
          = (Source)obj;
                  
          // 注意是直接調用,沒有通過reflect
                  target.setValue((BigInteger)converter.convert(new Integer(source.getValue()), CGLIB$load_class$java$2Emath$2EBigInteger, "setValue")); 
              }

          }

          使用注意

          1. 避免每次進行BeanCopier.create創建對象,一般建議是通過static BeanCopier copier = BeanCopier.create()
          2. 合理使用converter。
          3. 應用場景:兩個對象之間同名同屬性的數據拷貝, 不能單獨針對其中的幾個屬性單獨拷貝

          BulkBean

               相比于BeanCopier,BulkBean將整個Copy的動作拆分為getPropertyValues,setPropertyValues的兩個方法,允許自定義處理的屬性。

           

          public class BulkBeanTest {

              
          public static void main(String args[]) {
                  System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, 
          "/home/ljh/cglib");
                  String[] getter 
          = new String[] "getValue" };
                  String[] setter 
          = new String[] "setValue" };
                  Class[] clazzs 
          = new Class[] int.class };

                  BulkBean bean 
          = BulkBean.create(BulkSource.class, getter, setter, clazzs);
                  BulkSource obj 
          = new BulkSource();
                  obj.setValue(
          1);

                  Object[] objs 
          = bean.getPropertyValues(obj);
                  
          for (Object tmp : objs) {
                      System.out.println(tmp);
                  }

              }

          }

          class BulkSource {
              
          private int value;
              ..
          }


          // 反編譯后的代碼: 
           public void getPropertyValues(Object obj, Object aobj[])
              
          {
                  BulkSource bulksource 
          = (BulkSource)obj;
                  aobj[
          0= new Integer(bulksource.getValue());
              }

           

           

           

          使用注意

          1. 避免每次進行BulkBean.create創建對象,一般建議是通過static BulkBean.create copier = BulkBean.create
          2. 應用場景:針對特定屬性的get,set操作,一般適用通過xml配置注入和注出的屬性,運行時才確定處理的Source,Target類,只需關注屬性名即可。

          BeanMap

          相比于BeanCopier,BulkBean,都是針對兩個Pojo Bean進行處理,那如果對象一個是Pojo Bean和Map對象之間,那就得看看BeanMap,將一個java bean允許通過map的api進行調用。
          幾個支持的操作接口:

          • Object get(Object key)
          • Object put(Object key, Object value)
          • void putAll(Map t)
          • Set entrySet()
          • Collection values()
          • boolean containsKey(Object key)
          • ....
          public class BeanMapTest {

              
          public static void main(String args[]) {
                  
          // 初始化
                  BeanMap map = BeanMap.create(new Pojo());
                  
          // 構造
                  Pojo pojo = new Pojo();
                  pojo.setIntValue(
          1);
                  pojo.setBigInteger(
          new BigInteger("2"));
                  
          // 賦值
                  map.setBean(pojo);
                  
          // 驗證
                  System.out.println(map.get("intValue"));
                  System.out.println(map.keySet());
                  System.out.println(map.values());
              }

          }


          class Pojo {

              
          private int        intValue;
              
          private BigInteger bigInteger;
              .
          }


          //反編譯代碼查看:
          //首先保存了所有的屬性到一個set中
          private static FixedKeySet keys = new FixedKeySet(new String[] {
                  
          "bigInteger""intValue"
              }
          );
          public Object get(Object obj, Object obj1)
              
          {
                  (Pojo)obj;
                  String s 
          = (String)obj1;
                  s;
                  s.hashCode();
                  JVM INSTR lookupswitch 
          2default 72
              
          //                   -139068386: 40
              
          //                   556050114: 52;
                     goto _L1 _L2 _L3
          _L2:
                  
          "bigInteger";
           
          //屬性判斷是否相等
                  equals();
                  JVM INSTR ifeq 
          73;
                     
          goto _L4 _L5
          _L5:
                  
          break MISSING_BLOCK_LABEL_73;
          _L4:
                  getBigInteger();
                  
          return;
          _L3:

          .

          }

          使用注意

          1. 避免每次進行BeanMap map = BeanMap.create();創建對象,不同于BeanCopier對象,BeanMap主要針對對象實例進行處理,所以一般建議是map.setBean(pojo);進行動態替換持有的對象實例。
          2. 應用場景:針對put,putAll操作會直接修改pojo對象里的屬性,所以可以通過beanMap.putAll(map)進行map<->pojo屬性的拷貝。

           

          BeanGenerator

             暫時沒有想到合適的使用場景,不過BeanGenerator使用概念是很簡單的,就是將一個Map<String,Class>properties的屬性定義,動態生成一個pojo bean類。

           

          BeanGenerator generator = new BeanGenerator();
          generator.addProperty(
          "intValue"int.class);
          generator.addProperty(
          "integer", Integer.class);
          generator.addProperty(
          "properties", Properties.class);
                 
          Class clazz 
          = (Class) generator.createClass();
          Object obj 
          = generator.create();

          PropertyDescriptor[] getters 
          = ReflectUtils.getBeanGetters(obj.getClass());
          for (PropertyDescriptor getter : getters) {
              Method write 
          = getter.getWriteMethod();
              System.out.println(write.getName());
          }

           

          ImmutableBean

          bean Immutable模式的一種動態class實現,Immutable模式主要應用于服務設計上,返回的pojo bean對象,不運行進行write方法調用

          說明

          個人是不太建議使用cglib動態class的方式來實現bean Immutable的模式,Immutable模式應該是一種服務接口上的顯示聲明,而不是如此隱晦,而且pojo bean盡量做到是輕量級,簡答的set/get方法,如果要做充血的領域模型那就另當別論了。

           

          reflect (class,method處理)

          FastClass

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

          public class FastClassTest {
              
          public static void main(String args[]) throws Exception {
                  System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, 
          "/home/ljh/cglib");

                  FastClass clazz 
          = FastClass.create(FastSource.class);
                  
          // fast class反射調用
                  FastSource obj = (FastSource) clazz.newInstance();
                  clazz.invoke(
          "setValue"new Class[] int.class }, obj, new Object[] 1 });
                  clazz.invoke(
          "setOther"new Class[] int.class }, obj, new Object[] 2 });

                  
          int value = (Integer) clazz.invoke("getValue"new Class[] {}, obj, new Object[] {});
                  
          int other = (Integer) clazz.invoke("getOther"new Class[] {}, obj, new Object[] {});
                  System.out.println(value 
          + " " + other);
                  
          // fastMethod使用
                  FastMethod setValue = clazz.getMethod("setValue"new Class[] int.class });
                  System.out.println(
          "setValue index is : " + setValue.getIndex());

                  FastMethod getValue 
          = clazz.getMethod("getValue"new Class[] {});
                  System.out.println(
          "getValue index is : " + getValue.getIndex());

                  FastMethod setOther 
          = clazz.getMethod("setOther"new Class[] int.class });
                  System.out.println(
          "setOther index is : " + setOther.getIndex());

                  FastMethod getOther 
          = clazz.getMethod("getOther"new Class[] {});
                  System.out.println(
          "getOther index is : " + getOther.getIndex());
                  
          // 其他
                  System.out.println("getDeclaredMethods : " + clazz.getJavaClass().getDeclaredMethods().length);
                  System.out.println(
          "getConstructors : " + clazz.getJavaClass().getConstructors().length);
                  System.out.println(
          "getFields : " + clazz.getJavaClass().getFields().length);
                  System.out.println(
          "getMaxIndex : " + clazz.getMaxIndex());
              }

          }


          class FastSource {
              
          private int value;
              
          private int other;

          }

          proxy (spring aop相關)

          總體類結構圖:

           

          Callback & CallbackGenerator

          1. MethodInterceptor
            • 類似于spring aop的around Advise的功能,大家都知道,不多做介紹。唯一需要注意的就是proxy.invokeSuper和proxy.invoke的區別。invokeSuper是退出當前interceptor的處理,進入下一個callback處理,invoke則會繼續回調該方法,如果傳遞給invoke的obj參數出錯容易造成遞歸調用
          2. Dispatcher, ProxyRefDispatcher
            • 類似于delegate的模式,直接將請求分發給具體的Dispatcher調用,是否有著接口+實現分離的味道,將接口的方法調用通過Dispatcher轉到實現target上。ProxyRefDispatcher與Dispatcher想比,loadObject()多了個當前代理對象的引用。
            •         
              //反編譯的部分代碼
              public final int cal(int i, int j)
              {
                      CGLIB$CALLBACK_1;
                      
              if(CGLIB$CALLBACK_1 != nullgoto _L2; else goto _L1
              _L1:
                      JVM INSTR pop ;
                      CGLIB$BIND_CALLBACKS(
              this);
                      CGLIB$CALLBACK_1;
              _L2:
                      loadObject(); 
              //每次都進行調用
                      (DefaultCalcService);
                      i;
                      j;
                      cal(); 
              //調用實現類的方法
                      return;
                  }
               
               
          3. LazyLoader
            • 相比于Dispatcher,lazyLoader在第一次獲取了loadObject后,會進行緩存,后續的請求調用都會直接調用該緩存的屬性.
            •         
              //反編譯部分代碼
              public final int cal(int i, int j)
              {
                  
              this;
                  
              return ((DefaultCalcService)CGLIB$LOAD_PRIVATE_3()).cal(i, j);
              }


              private final synchronized Object CGLIB$LOAD_PRIVATE_3()
              {
                      CGLIB$LAZY_LOADER_3; 
              //保存的屬性
                      if(CGLIB$LAZY_LOADER_3 != nullgoto _L2; else goto _L1
              _L1:
                      JVM INSTR pop ;
                      
              this;
                      CGLIB$CALLBACK_3;
                      
              if(CGLIB$CALLBACK_3 != nullgoto _L4; else goto _L3
              _L3:
                      JVM INSTR pop ;
                      CGLIB$BIND_CALLBACKS(
              this);
                      CGLIB$CALLBACK_3;
              _L4:
                      loadObject();
                      JVM INSTR dup_x1 ;
                      CGLIB$LAZY_LOADER_3;
              _L2:
                      
              return;
                  }
          4. NoOp
            • 不做任何處理,結合Filter針對不需要做代理方法直接返回,調用其原始方法
          5. FixedValue
            • 強制方法返回固定值,可結合Filter進行控制
          6. InvocationHandler(提供和jdk proxy的功能),不常用

          CallbackFilter

          主要的作用就是callback調度,主要的一個方法:int accept(Method method);
          返回的int在int值,代表對應method需要插入的callback,會靜態生成到class的代碼中,這樣是cglib proxy區別于jdk proxy的方式,一個是靜態的代碼調用,一個是動態的reflect。
          可以查看: Enhancer類中的emitMethods方法,line:883。在構造class method字節嗎之前就已經確定需要運行的callback。

           

          Enhancer

          System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/ljh/cglib");
          LogInteceptor logInteceptor 
          = new LogInteceptor();
          CalDispatcher calDispatcher 
          = new CalDispatcher();
          CalcProxyRefDispatcher calcProxyRefDispatcher 
          = new CalcProxyRefDispatcher();
          LazyLoaderCallback lazyLoaderCallback 
          = new LazyLoaderCallback();

          Enhancer enhancer 
          = new Enhancer();
          enhancer.setSuperclass(CalcService.
          class); //接口類
          enhancer.setCallbacks(new Callback[] { logInteceptor, calDispatcher, calcProxyRefDispatcher,lazyLoaderCallback, NoOp.INSTANCE }); // callback數組
          enhancer.setCallbackFilter(new CalcCallbackFilter()); // filter
          CalcService service = (CalcService) enhancer.create();

          int result = service.cal(11);

          Util  (工具類,感覺有點雞肋)

          • StringSwitcher 提供string和int的map映射查詢,給定一個string字符串,返回同個下標數組的int值,感覺很雞肋,用Map不是可以很快速的實現功能
          • ParallelSorter 看了具體的代碼,沒啥意思,就是提供了一個二分的快速排序和多路歸并排序。沒有所謂的并行排序,原本以為會涉及多線程處理,可惜沒有

          transform

               暫時沒仔細研究,更多的是對asm的封裝,等下次看了asm代碼后再回來研究下。



          Blog : http://agapple.javaeye.com/  歡迎訪問
          posted on 2010-11-01 22:24 agapple 閱讀(1317) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 吴川市| 五河县| 宜兰县| 布尔津县| 墨玉县| 大竹县| 额济纳旗| 扬中市| 丹寨县| 宜阳县| 宁安市| 临清市| 青浦区| 慈溪市| 洪湖市| 通化县| 江达县| 炎陵县| 静海县| 宜宾县| 怀集县| 山东| 福安市| 灵寿县| 宁都县| 武胜县| 长治县| 茌平县| 嘉禾县| 新平| 徐汇区| 永川市| 葫芦岛市| 比如县| 铁岭县| 友谊县| 南丰县| 常宁市| 华容县| 虎林市| 尚义县|