天天看點

cglib源碼學習交流

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

    本文主要通過對cglib有原理的分析,反編譯檢視源碼,例子等方式做一個介紹。

cglib基本資訊

cglib的官方網站:  http://cglib.sourceforge.net/

cglib目前的最新版本應該是2.2,公司普遍使用的版本也是這個

官網的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

MethodInterceptor   , Dispatcher, LazyLoader , ProxyRefDispatcher , NoOp , FixedValue , InvocationHandler(提供和jdk proxy的功能)

Enhancer

CallbackGenerator

Callback

CallbackFilter

util

StringSwitcher 

ParallelSorter 

transform 

core核心代碼部分

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

處理jdk reflect的工具類,比如擷取一個類所有的Method,擷取構造函數資訊等。

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

類庫中重要的唯一辨別生成器,用于cglib做cache時做map key,比較底層的基礎類。

例子:

說明:

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

預設的實作類:DefaultNamingPolicy, 具體cglib動态生成類的命名控制。

一般的命名規則:

被代理class name + "

"+使用cglib處理的classname+"ByCGLIB"+"

" + key的hashcode

示例:FastSource $$FastClass ByCGLIB$$e1a36bab.class

預設的實作類: DefaultGeneratorStrategy

控制ClassGenerator生成class的byte資料,中間可插入自己的處理。注意這裡依賴了:DebuggingClassWriter進行class generator的處理

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

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

外部的BeanCopier都包含了一Generator,繼承自AbstractClassGenerator,實作了generateClass(ClassVisitor v),Object firstInstance(Class type)方法。

AbstractClassGenerator自身會根據Source進行cache,是以針對已經生成過的class,這裡KeyFactory對應的值要相等,則會直接傳回cache中的結果。是以BeanCopier每次create慢隻是每次都需要new兩個對象,一個是KeyFactory.newInstance,另一個是firstInstance方法調用生成一個對象。

反編譯tips

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

Java代碼   

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

 可參見 cores/DebuggingClassWriter代碼。說明:這樣cglib會将動态生成的每個class都輸出到檔案中,然後我們可以通過decomp 進行反編譯檢視源碼。

beans (相關操作類)

簡單的示例代碼就不做介紹,相信大家都指導怎麼用,這裡主要介紹下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使用例子代碼   

public class BeanCopierTest {  

    public static void main(String args[]) {  

        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/tmp/1" );  

        BeanCopier copier = BeanCopier.create(Source.class, Target.class, true);  

        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" ));   

避免每次進行BeanCopier.create建立對象,一般建議是通過static BeanCopier copier = BeanCopier.create()

合理使用converter。

應用場景:兩個對象之間同名同屬性的資料拷貝,  不能單獨針對其中的幾個屬性單獨拷貝

     相比于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());  

避免每次進行BulkBean.create建立對象,一般建議是通過static BulkBean.create copier = BulkBean.create

應用場景:針對特定屬性的get,set操作,一般适用通過xml配置注入和注出的屬性,運作時才确定處理的Source,Target類,隻需關注屬性名即可。

相比于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 {  

        // 初始化   

        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 2 :  default   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:  

....  

避免每次進行BeanMap map = BeanMap.create();建立對象,不同于BeanCopier對象,BeanMap主要針對對象執行個體進行處理,是以一般建議是map.setBean(pojo);進行動态替換持有的對象執行個體。

應用場景:針對put,putAll操作會直接修改pojo對象裡的屬性,是以可以通過beanMap.putAll(map)進行map<->pojo屬性的拷貝。

   暫時沒有想到合适的使用場景,不過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());  

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

說明 

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

reflect (class,method處理)

顧明思義,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 {  

        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  other;  

proxy (spring aop相關)

總體類結構圖:

MethodInterceptor

類似于spring aop的around Advise的功能,大家都知道,不多做介紹。唯一需要注意的就是proxy.invokeSuper和proxy.invoke的差別。invokeSuper是退出目前interceptor的處理,進入下一個callback處理,invoke則會繼續回調該方法,如果傳遞給invoke的obj參數出錯容易造成遞歸調用

Dispatcher, ProxyRefDispatcher

類似于delegate的模式,直接将請求分發給具體的Dispatcher調用,是否有着接口+實作分離的味道,将接口的方法調用通過Dispatcher轉到實作target上。ProxyRefDispatcher與Dispatcher想比,loadObject()多了個目前代理對象的引用。

反編譯的部分代碼代碼   

//反編譯的部分代碼  

public final int cal(int i, int j)  

        CGLIB$CALLBACK_1;  

        if(CGLIB$CALLBACK_1 != null) goto _L2; else goto _L1  

_L1:  

        JVM INSTR pop ;  

        CGLIB$BIND_CALLBACKS(this);  

        loadObject(); //每次都進行調用  

        (DefaultCalcService);  

        i;  

        j;  

        cal(); //調用實作類的方法  

        return;  

    }   

LazyLoader

相比于Dispatcher,lazyLoader在第一次擷取了loadObject後,會進行緩存,後續的請求調用都會直接調用該緩存的屬性.

反編譯部分代碼代碼   

//反編譯部分代碼  

    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 != null) goto _L2; else goto _L1  

        this;  

        CGLIB$CALLBACK_3;  

        if(CGLIB$CALLBACK_3 != null) goto _L4; else goto _L3  

        loadObject();  

        JVM INSTR dup_x1 ;  

        CGLIB$LAZY_LOADER_3;  

NoOp

不做任何處理,結合Filter針對不需要做代理方法直接傳回,調用其原始方法

FixedValue

強制方法傳回固定值,可結合Filter進行控制

InvocationHandler(提供和jdk proxy的功能),不常用

主要的作用就是callback排程,主要的一個方法:int accept(Method method);  

傳回的int在int值,代表對應method需要插入的callback,會靜态生成到class的代碼中,這樣是cglib proxy差別于jdk proxy的方式,一個是靜态的代碼調用,一個是動态的reflect。

可以檢視: Enhancer類中的emitMethods方法,line:883。在構造class method位元組嗎之前就已經确定需要運作的callback。

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( 1 ,  1 );  

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

StringSwitcher 提供string和int的map映射查詢,給定一個string字元串,傳回同個下标數組的int值,感覺很雞肋,用Map不是可以很快速的實作功能

ParallelSorter 看了具體的代碼,沒啥意思,就是提供了一個二分的快速排序和多路歸并排序。沒有所謂的并行排序,原本以為會涉及多線程處理,可惜沒有

transform

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

繼續閱讀