背景
前段時間在工作中,包括一些代碼閱讀過程中,spring aop經(jīng)常性的會看到cglib中的相關內(nèi)容,包括BeanCopier,BulkBean,Enancher等內(nèi)容,以前雖大致知道一些內(nèi)容,原理是通過bytecode,但沒具體深入代碼研究,只知其所用不知其所以然,所以就特地花了半天多的工作時間研究了CGLIB的相關源碼,同時結(jié)合看了下 spring Aop中對CGLIB的使用。
cglib基本信息
- cglib的官方網(wǎng)站: http://cglib.sourceforge.net/
- cglib目前的最新版本應該是2.2,公司普遍使用的版本也是這個
- 官網(wǎng)的samples : http://cglib.sourceforge.net/xref/samples/
cglib代碼包結(jié)構
- core (核心代碼)
- EmitUtils
- ReflectUtils
- KeyFactory
- ClassEmitter/CodeEmitter
- NamingPolicy/DefaultNamingPolicy
- GeneratorStrategy/DefaultGeneratorStrategy
- DebuggingClassWriter
- ClassGenerator/AbstractClassGenerator
- beans (bean操作類)
- BeanCopier
- BulkBean
- BeanMap
- ImmutableBean
- BeanGenerator
- reflect
- proxy
- Enhancer
- CallbackGenerator
- Callback
- MethodInterceptor , Dispatcher, LazyLoader , ProxyRefDispatcher , NoOp , FixedValue , InvocationHandler(提供和jdk proxy的功能)
- CallbackFilter
- util
- StringSwitcher
- ParallelSorter
- transform
core核心代碼部分
EmitUtils
重要的工具類,主要封裝了一些操作bytecode的基本函數(shù),比如生成一個null_constructor,添加類屬性add_property等
ReflectUtils
處理jdk reflect的工具類,比如獲取一個類所有的Method,獲取構造函數(shù)信息等。
ClassEmitter/CodeEmitter
對asm的classAdapter和MethodAdapter的實現(xiàn),貫穿于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方法,但具體的參數(shù)可以隨意定義,通過newInstance返回的為一個唯一標示,只有當傳入的所有參數(shù)的equals都返回true時,生成的key才是相同的,這就相當于多key的概念。
NamingPolicy
默認的實現(xiàn)類:DefaultNamingPolicy, 具體cglib動態(tài)生成類的命名控制。
一般的命名規(guī)則:
- 被代理class name + "$$" + 使用cglib處理的class name + "ByCGLIB" + "$$" + key的hashcode
- 示例:FastSource$$FastClassByCGLIB$$e1a36bab.class
GeneratorStrategy
默認的實現(xiàn)類: DefaultGeneratorStrategy
控制ClassGenerator生成class的byte數(shù)據(jù),中間可插入自己的處理。注意這里依賴了:DebuggingClassWriter進行class generator的處理
DebuggingClassWriter
cglib封裝asm的處理類,用于生成class的byte流,通過GeneratorStrategy回調(diào)ClassGenerator.generateClass(DebuggingClassWriter),將自定義的class byte處理回調(diào)給具體的cglib上層操作類,比如由具體的BeanCopier去控制bytecode的生成。
ClassGenerator
其中一個抽象實現(xiàn):AbstractClassGenerator。cglib代碼中核心的Class bytecode操作主體,包含了一些cache,調(diào)用NamingPolicy,GeneratorStrategy進行處理,可以說是一個最核心的調(diào)度者。
對應的類圖:

- 外部的BeanCopier都包含了一Generator,繼承自AbstractClassGenerator,實現(xiàn)了generateClass(ClassVisitor v),Object firstInstance(Class type)方法。
- AbstractClassGenerator自身會根據(jù)Source進行cache,所以針對已經(jīng)生成過的class,這里KeyFactory對應的值要相等,則會直接返回cache中的結(jié)果。所以BeanCopier每次create慢只是每次都需要new兩個對象,一個是KeyFactory.newInstance,另一個是firstInstance方法調(diào)用生成一個對象。
反編譯tips
大家都知道cglib是進行bytecode操作,會動態(tài)生成class,最快最直接的學習就是結(jié)合他生成的class,對照代碼進行學習,效果會好很多。
system.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "指定輸出目錄");
可參見 cores/DebuggingClassWriter代碼。說明:這樣cglib會將動態(tài)生成的每個class都輸出到文件中,然后我們可以通過decomp進行反編譯查看源碼。
beans (相關操作類)
BeanCopier
簡單的示例代碼就不做介紹,相信大家都指導怎么用,這里主要介紹下Convert的使用。
- 許多網(wǎng)友都做過BeanCopier,BeanUtils的測試,基本BeanCopier的性能是BeanUtils的10倍以上。,出了反射這一性能差異外,BeanUtils默認是開啟Converter功能,允許同名,不同類型的屬性進行拷貝,比如Date對象到String屬性。
- 有興趣的同學可以去比較下PropertyUtils,默認不開啟Converter功能,發(fā)現(xiàn)性能是BeanUtils的2倍多。
初始化例子:BeanCopier copier = BeanCopier.create(Source.class, Target.class, true);
第三個參數(shù)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.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類中的定義的參數(shù)對象
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;
// 注意是直接調(diào)用,沒有通過reflect
target.setValue((BigInteger)converter.convert(new Integer(source.getValue()), CGLIB$load_class$java$2Emath$2EBigInteger, "setValue"));
}
}

使用注意
- 避免每次進行BeanCopier.create創(chuàng)建對象,一般建議是通過static BeanCopier copier = BeanCopier.create()
- 合理使用converter。
- 應用場景:兩個對象之間同名同屬性的數(shù)據(jù)拷貝, 不能單獨針對其中的幾個屬性單獨拷貝
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());
}

使用注意
- 避免每次進行BulkBean.create創(chuàng)建對象,一般建議是通過static BulkBean.create copier = BulkBean.create
- 應用場景:針對特定屬性的get,set操作,一般適用通過xml配置注入和注出的屬性,運行時才確定處理的Source,Target類,只需關注屬性名即可。
BeanMap
相比于BeanCopier,BulkBean,都是針對兩個Pojo Bean進行處理,那如果對象一個是Pojo Bean和Map對象之間,那就得看看BeanMap,將一個java bean允許通過map的api進行調(diào)用。
幾個支持的操作接口:
- 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 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();創(chuàng)建對象,不同于BeanCopier對象,BeanMap主要針對對象實例進行處理,所以一般建議是map.setBean(pojo);進行動態(tài)替換持有的對象實例。
- 應用場景:針對put,putAll操作會直接修改pojo對象里的屬性,所以可以通過beanMap.putAll(map)進行map<->pojo屬性的拷貝。
BeanGenerator
暫時沒有想到合適的使用場景,不過BeanGenerator使用概念是很簡單的,就是將一個Map<String,Class>properties的屬性定義,動態(tài)生成一個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模式的一種動態(tài)class實現(xiàn),Immutable模式主要應用于服務設計上,返回的pojo bean對象,不運行進行write方法調(diào)用
說明
個人是不太建議使用cglib動態(tài)class的方式來實現(xiàn)bean Immutable的模式,Immutable模式應該是一種服務接口上的顯示聲明,而不是如此隱晦,而且pojo bean盡量做到是輕量級,簡答的set/get方法,如果要做充血的領域模型那就另當別論了。
|
reflect (class,method處理)
FastClass
顧明思義,F(xiàn)astClass就是對Class對象進行特定的處理,比如通過數(shù)組保存method引用,因此FastClass引出了一個index下標的新概念,比如getIndex(String name, Class[] parameterTypes)就是以前的獲取method的方法。
通過數(shù)組存儲method,constructor等class信息,從而將原先的反射調(diào)用,轉(zhuǎn)化為class.index的直接調(diào)用,從而體現(xiàn)所謂的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反射調(diào)用
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相關)
總體類結(jié)構圖:

Callback & CallbackGenerator
- MethodInterceptor
- 類似于spring aop的around Advise的功能,大家都知道,不多做介紹。唯一需要注意的就是proxy.invokeSuper和proxy.invoke的區(qū)別。invokeSuper是退出當前interceptor的處理,進入下一個callback處理,invoke則會繼續(xù)回調(diào)該方法,如果傳遞給invoke的obj參數(shù)出錯容易造成遞歸調(diào)用
- Dispatcher, ProxyRefDispatcher
- LazyLoader
- 相比于Dispatcher,lazyLoader在第一次獲取了loadObject后,會進行緩存,后續(xù)的請求調(diào)用都會直接調(diào)用該緩存的屬性.
-
//反編譯部分代碼
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 != null) goto _L2; else goto _L1
_L1:
JVM INSTR pop ;
this;
CGLIB$CALLBACK_3;
if(CGLIB$CALLBACK_3 != null) goto _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;
}
- NoOp
- 不做任何處理,結(jié)合Filter針對不需要做代理方法直接返回,調(diào)用其原始方法
- FixedValue
- 強制方法返回固定值,可結(jié)合Filter進行控制
- InvocationHandler(提供和jdk proxy的功能),不常用
CallbackFilter
主要的作用就是callback調(diào)度,主要的一個方法:int accept(Method method);
返回的int在int值,代表對應method需要插入的callback,會靜態(tài)生成到class的代碼中,這樣是cglib proxy區(qū)別于jdk proxy的方式,一個是靜態(tài)的代碼調(diào)用,一個是動態(tài)的reflect。
可以查看: Enhancer類中的emitMethods方法,line:883。在構造class method字節(jié)嗎之前就已經(jīng)確定需要運行的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數(shù)組
enhancer.setCallbackFilter(new CalcCallbackFilter()); // filter
CalcService service = (CalcService) enhancer.create();

int result = service.cal(1, 1);
Util (工具類,感覺有點雞肋)
- StringSwitcher 提供string和int的map映射查詢,給定一個string字符串,返回同個下標數(shù)組的int值,感覺很雞肋,用Map不是可以很快速的實現(xiàn)功能
- ParallelSorter 看了具體的代碼,沒啥意思,就是提供了一個二分的快速排序和多路歸并排序。沒有所謂的并行排序,原本以為會涉及多線程處理,可惜沒有
transform
暫時沒仔細研究,更多的是對asm的封裝,等下次看了asm代碼后再回來研究下。
Blog :
http://agapple.javaeye.com/ 歡迎訪問