原文地址:http://www.iteye.com/topic/799827
背景
前段時間在工作中,包括一些代碼閱讀過程中,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
- 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進行處理,可以說是一個最核心的調度者。
對應的類圖:

- 外部的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,對照代碼進行學習,效果會好很多。
- 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.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。
- 應用場景:兩個對象之間同名同屬性的數據拷貝, 不能單獨針對其中的幾個屬性單獨拷貝
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創建對象,一般建議是通過static BulkBean.create copier = BulkBean.create
- 應用場景:針對特定屬性的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 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
暫時沒有想到合適的使用場景,不過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
- 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);
- CGLIB$CALLBACK_1;
- _L2:
- loadObject(); //每次都進行調用
- (DefaultCalcService);
- i;
- j;
- cal(); //調用實現類的方法
- return;
- }
- 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 != 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
- 不做任何處理,結合Filter針對不需要做代理方法直接返回,調用其原始方法
- FixedValue
- 強制方法返回固定值,可結合Filter進行控制
- 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(1, 1);
Util (工具類,感覺有點雞肋)
- StringSwitcher 提供string和int的map映射查詢,給定一個string字符串,返回同個下標數組的int值,感覺很雞肋,用Map不是可以很快速的實現功能
- ParallelSorter 看了具體的代碼,沒啥意思,就是提供了一個二分的快速排序和多路歸並排序。沒有所謂的並行排序,原本以為會涉及多線程處理,可惜沒有
transform
暫時沒仔細研究,更多的是對asm的封裝,等下次看了asm代碼后再回來研究下。