cglib源碼分析--轉


原文地址:http://www.iteye.com/topic/799827

背景

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

    本文主要通過對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,對照代碼進行學習,效果會好很多。

Java代碼   收藏代碼
  1. 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,否則就會報錯。

Converter使用例子代碼   收藏代碼
  1. public class BeanCopierTest {  
  2.   
  3.     public static void main(String args[]) {  
  4.         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/tmp/1");  
  5.         BeanCopier copier = BeanCopier.create(Source.class, Target.class, true);  
  6.         Source from = new Source();  
  7.         from.setValue(1);  
  8.   
  9.         Target to = new Target();  
  10.         Converter converter = new BigIntConverter();  
  11.         copier.copy(from, to, converter); //使用converter類  
  12.   
  13.         System.out.println(to.getValue());  
  14.     }  
  15. }  
  16.   
  17. class BigIntConverter implements net.sf.cglib.core.Converter {  
  18.   
  19.     @Override  
  20.     public Object convert(Object value, Class target, Object context) {  
  21.         System.out.println(value.getClass() + " " + value); // from類中的value對象  
  22.         System.out.println(target); // to類中的定義的參數對象  
  23.         System.out.println(context.getClass() + " " + context); // String對象,具體的方法名  
  24.         if (target.isAssignableFrom(BigInteger.class)) {  
  25.             return new BigInteger(value.toString());  
  26.         } else {  
  27.             return value;  
  28.         }  
  29.     }  
  30.   
  31. }  
  32. ----  
  33. 反編譯后看的代碼:  
  34. public class Target$$BeanCopierByCGLIB$$e1c34377 extends BeanCopier  
  35. {  
  36.     public void copy(Object obj, Object obj1, Converter converter)  
  37.     {  
  38.         Target target = (Target)obj1;  
  39.         Source source = (Source)obj;  
  40.         // 注意是直接調用,沒有通過reflect  
  41.         target.setValue((BigInteger)converter.convert(new Integer(source.getValue()), CGLIB$load_class$java$2Emath$2EBigInteger, "setValue"));   
  42.     }  
  43. }  

 

使用注意

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

BulkBean

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

 

Java代碼   收藏代碼
  1. public class BulkBeanTest {  
  2.   
  3.     public static void main(String args[]) {  
  4.         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/ljh/cglib");  
  5.         String[] getter = new String[] { "getValue" };  
  6.         String[] setter = new String[] { "setValue" };  
  7.         Class[] clazzs = new Class[] { int.class };  
  8.   
  9.         BulkBean bean = BulkBean.create(BulkSource.class, getter, setter, clazzs);  
  10.         BulkSource obj = new BulkSource();  
  11.         obj.setValue(1);  
  12.   
  13.         Object[] objs = bean.getPropertyValues(obj);  
  14.         for (Object tmp : objs) {  
  15.             System.out.println(tmp);  
  16.         }  
  17.     }  
  18. }  
  19. class BulkSource {  
  20.     private int value;  
  21.     .....  
  22. }  
  23.   
  24. // 反編譯后的代碼:   
  25.  public void getPropertyValues(Object obj, Object aobj[])  
  26.     {  
  27.         BulkSource bulksource = (BulkSource)obj;  
  28.         aobj[0] = new Integer(bulksource.getValue());  
  29.     }  

 

使用注意

  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)
  • ....
Java代碼   收藏代碼
  1. public class BeanMapTest {  
  2.   
  3.     public static void main(String args[]) {  
  4.         // 初始化  
  5.         BeanMap map = BeanMap.create(new Pojo());  
  6.         // 構造  
  7.         Pojo pojo = new Pojo();  
  8.         pojo.setIntValue(1);  
  9.         pojo.setBigInteger(new BigInteger("2"));  
  10.         // 賦值  
  11.         map.setBean(pojo);  
  12.         // 驗證  
  13.         System.out.println(map.get("intValue"));  
  14.         System.out.println(map.keySet());  
  15.         System.out.println(map.values());  
  16.     }  
  17. }  
  18.   
  19. class Pojo {  
  20.   
  21.     private int        intValue;  
  22.     private BigInteger bigInteger;  
  23.     ....  
  24. }  
  25.   
  26. //反編譯代碼查看:  
  27. //首先保存了所有的屬性到一個set中  
  28. private static FixedKeySet keys = new FixedKeySet(new String[] {  
  29.         "bigInteger", "intValue"  
  30.     });  
  31. public Object get(Object obj, Object obj1)  
  32.     {  
  33.         (Pojo)obj;  
  34.         String s = (String)obj1;  
  35.         s;  
  36.         s.hashCode();  
  37.         JVM INSTR lookupswitch 2: default 72  
  38.     //                   -139068386: 40  
  39.     //                   556050114: 52;  
  40.            goto _L1 _L2 _L3  
  41. _L2:  
  42.         "bigInteger";  
  43.  //屬性判斷是否相等  
  44.         equals();  
  45.         JVM INSTR ifeq 73;  
  46.            goto _L4 _L5  
  47. _L5:  
  48.         break MISSING_BLOCK_LABEL_73;  
  49. _L4:  
  50.         getBigInteger();  
  51.         return;  
  52. _L3:  
  53.   
  54. ....  
  55.   
  56. }  

 

 

使用注意

  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類。

 

Java代碼   收藏代碼
  1. BeanGenerator generator = new BeanGenerator();  
  2. generator.addProperty("intValue", int.class);  
  3. generator.addProperty("integer", Integer.class);  
  4. generator.addProperty("properties", Properties.class);  
  5.          
  6. Class clazz = (Class) generator.createClass();  
  7. Object obj = generator.create();  
  8.   
  9. PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(obj.getClass());  
  10. for (PropertyDescriptor getter : getters) {  
  11.     Method write = getter.getWriteMethod();  
  12.     System.out.println(write.getName());  
  13. }  

 

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。

Java代碼   收藏代碼
  1. public class FastClassTest {  
  2.     public static void main(String args[]) throws Exception {  
  3.         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/ljh/cglib");  
  4.   
  5.         FastClass clazz = FastClass.create(FastSource.class);  
  6.         // fast class反射調用  
  7.         FastSource obj = (FastSource) clazz.newInstance();  
  8.         clazz.invoke("setValue", new Class[] { int.class }, obj, new Object[] { 1 });  
  9.         clazz.invoke("setOther", new Class[] { int.class }, obj, new Object[] { 2 });  
  10.   
  11.         int value = (Integer) clazz.invoke("getValue", new Class[] {}, obj, new Object[] {});  
  12.         int other = (Integer) clazz.invoke("getOther", new Class[] {}, obj, new Object[] {});  
  13.         System.out.println(value + " " + other);  
  14.         // fastMethod使用  
  15.         FastMethod setValue = clazz.getMethod("setValue", new Class[] { int.class });  
  16.         System.out.println("setValue index is : " + setValue.getIndex());  
  17.   
  18.         FastMethod getValue = clazz.getMethod("getValue", new Class[] {});  
  19.         System.out.println("getValue index is : " + getValue.getIndex());  
  20.   
  21.         FastMethod setOther = clazz.getMethod("setOther", new Class[] { int.class });  
  22.         System.out.println("setOther index is : " + setOther.getIndex());  
  23.   
  24.         FastMethod getOther = clazz.getMethod("getOther", new Class[] {});  
  25.         System.out.println("getOther index is : " + getOther.getIndex());  
  26.         // 其他  
  27.         System.out.println("getDeclaredMethods : " + clazz.getJavaClass().getDeclaredMethods().length);  
  28.         System.out.println("getConstructors : " + clazz.getJavaClass().getConstructors().length);  
  29.         System.out.println("getFields : " + clazz.getJavaClass().getFields().length);  
  30.         System.out.println("getMaxIndex : " + clazz.getMaxIndex());  
  31.     }  
  32. }  
  33.   
  34. class FastSource {  
  35.     private int value;  
  36.     private int other;  
  37.   
  38. }  

 

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()多了個當前代理對象的引用。
    • 反編譯的部分代碼代碼   收藏代碼
      1. //反編譯的部分代碼  
      2. public final int cal(int i, int j)  
      3. {  
      4.         CGLIB$CALLBACK_1;  
      5.         if(CGLIB$CALLBACK_1 != null) goto _L2; else goto _L1  
      6. _L1:  
      7.         JVM INSTR pop ;  
      8.         CGLIB$BIND_CALLBACKS(this);  
      9.         CGLIB$CALLBACK_1;  
      10. _L2:  
      11.         loadObject(); //每次都進行調用  
      12.         (DefaultCalcService);  
      13.         i;  
      14.         j;  
      15.         cal(); //調用實現類的方法  
      16.         return;  
      17.     }   
  3. LazyLoader
    • 相比於Dispatcher,lazyLoader在第一次獲取了loadObject后,會進行緩存,后續的請求調用都會直接調用該緩存的屬性.
    • 反編譯部分代碼代碼   收藏代碼
      1. //反編譯部分代碼  
      2. public final int cal(int i, int j)  
      3. {  
      4.     this;  
      5.     return ((DefaultCalcService)CGLIB$LOAD_PRIVATE_3()).cal(i, j);  
      6. }  
      7.   
      8. private final synchronized Object CGLIB$LOAD_PRIVATE_3()  
      9. {  
      10.         CGLIB$LAZY_LOADER_3; //保存的屬性  
      11.         if(CGLIB$LAZY_LOADER_3 != null) goto _L2; else goto _L1  
      12. _L1:  
      13.         JVM INSTR pop ;  
      14.         this;  
      15.         CGLIB$CALLBACK_3;  
      16.         if(CGLIB$CALLBACK_3 != null) goto _L4; else goto _L3  
      17. _L3:  
      18.         JVM INSTR pop ;  
      19.         CGLIB$BIND_CALLBACKS(this);  
      20.         CGLIB$CALLBACK_3;  
      21. _L4:  
      22.         loadObject();  
      23.         JVM INSTR dup_x1 ;  
      24.         CGLIB$LAZY_LOADER_3;  
      25. _L2:  
      26.         return;  
      27.     }  
  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

Java代碼   收藏代碼
  1. System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/ljh/cglib");  
  2. LogInteceptor logInteceptor = new LogInteceptor();  
  3. CalDispatcher calDispatcher = new CalDispatcher();  
  4. CalcProxyRefDispatcher calcProxyRefDispatcher = new CalcProxyRefDispatcher();  
  5. LazyLoaderCallback lazyLoaderCallback = new LazyLoaderCallback();  
  6.   
  7. Enhancer enhancer = new Enhancer();  
  8. enhancer.setSuperclass(CalcService.class); //接口類  
  9. enhancer.setCallbacks(new Callback[] { logInteceptor, calDispatcher, calcProxyRefDispatcher,lazyLoaderCallback, NoOp.INSTANCE }); // callback數組  
  10. enhancer.setCallbackFilter(new CalcCallbackFilter()); // filter  
  11. CalcService service = (CalcService) enhancer.create();  
  12.   
  13. int result = service.cal(1, 1);  

 

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

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

 

transform

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


免責聲明!

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



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