前段時間在工作中,包括一些代碼閱讀過程中,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代碼後再回來研究下。