Java中通過Emit實現動態類生成
動態生成一個類對于AOP,O/R Mapping等技術非常有幫助。對于Java來說,問題不大,而對于.NET,則要麻煩些(主要麻煩在于實現代碼的生成需要IL),故猜測這可能也是在AOP, O/R Mapping方面,Java走得略前的原因吧。
麻煩歸麻煩,非不能也,動態生成一個簡單的類還不至于太難。
假設有如下接口:
希望能創建一個類生成器TypeCreator,并能以以下方式使用:
首先,發現System.Reflection.Emit.TypeBuilder似乎就是一個現成的類生成器。 不過TypeBuilder既沒有實用的static方法,也不能在外部實例化。不過ModuleBuilder倒有一個DefineType()方法,可以得到TypeBuilder;而ModuleBuilder和TyperBuilder一個德行,不能直接創建,得從AssemblyBuilder的DefineDynamicModule()方法得到。追根溯源,AssemblyBuilder得從AppDomain的DefineDynamicAssembly()的得來。最終好在AppDomain提供了一個靜態方法:AppDomain.CurrentDomain. 這一連串并非沒有道理,類型是依附于Module的,而Module依附于Assembly,而Assembly則被AppDomain裝載。所謂“皮之不存,毛將焉附”,為了創建Type這個“毛”,得先把Assembly,Module這些“皮”依次構造出來:
好了,測試一下試試看:
得到輸出:I'm moveingI'm eating。
總結
如果用于AOP的話,Emit可以動態生成一個裝飾類,相比于基于Remoting架構的TP/RP的方法,效率可能要高些,而且還能攔截new操作符。缺點:對于非Virtual的方法,似乎無法攔截。用于O/R Mapping的類生成,倒是不錯。
麻煩歸麻煩,非不能也,動態生成一個簡單的類還不至于太難。
假設有如下接口:
| interface IAnimal { void move(); void eat(); } |
希望能創建一個類生成器TypeCreator,并能以以下方式使用:
| TypeCreator tc=new TypeCreator(typeof(IAnimal)); Type t = tc.build(); IAnimal myAnimal= (IAnimal)Activator.CreateInstance(t); myAnimal.move(); myAnimal.eat(); |
首先,發現System.Reflection.Emit.TypeBuilder似乎就是一個現成的類生成器。 不過TypeBuilder既沒有實用的static方法,也不能在外部實例化。不過ModuleBuilder倒有一個DefineType()方法,可以得到TypeBuilder;而ModuleBuilder和TyperBuilder一個德行,不能直接創建,得從AssemblyBuilder的DefineDynamicModule()方法得到。追根溯源,AssemblyBuilder得從AppDomain的DefineDynamicAssembly()的得來。最終好在AppDomain提供了一個靜態方法:AppDomain.CurrentDomain. 這一連串并非沒有道理,類型是依附于Module的,而Module依附于Assembly,而Assembly則被AppDomain裝載。所謂“皮之不存,毛將焉附”,為了創建Type這個“毛”,得先把Assembly,Module這些“皮”依次構造出來:
| using System; using System.Reflection; using System.Reflection.Emit; public class TypeCreator { private Type targetType; /// <summary> /// 構造函數 /// </summary> /// <param name="targetType">被實現或者繼承的類型</param> public TypeCreator(Type targetType) { this.targetType = targetType; } public Type build() { //獲取當前AppDomain AppDomain currentAppDomain = AppDomain.CurrentDomain; //System.Reflection.AssemblyName 是用來表示一個Assembly的完整名稱的 AssemblyName assyName = new AssemblyName(); //為要創建的Assembly定義一個名稱(這里忽略版本號,Culture等信息) assyName.Name = "MyAssyFor_" + targetType.Name; //獲取AssemblyBuilder //AssemblyBuilderAccess有Run,Save,RunAndSave三個取值 AssemblyBuilder assyBuilder = currentAppDomain.DefineDynamicAssembly(assyName,AssemblyBuilderAccess.Run); //獲取ModuleBuilder,提供String參數作為Module名稱,隨便設一個 ModuleBuilder modBuilder = assyBuilder.DefineDynamicModule("MyModFor_"+targetType.Name); //新類型的名稱:隨便定一個 String newTypeName = "Imp_"+targetType.Name; //新類型的屬性:要創建的是Class,而非Interface,Abstract Class等,而且是Public的 TypeAttributes newTypeAttribute = TypeAttributes.Class | TypeAttributes.Public; //聲明要創建的新類型的父類型 Type newTypeParent; //聲明要創建的新類型要實現的接口 Type[] newTypeInterfaces; //對于基類型是否為接口,作不同處理 if(targetType.IsInterface) { newTypeParent = null; newTypeInterfaces = new Type[]{targetType}; } else { newTypeParent = targetType; newTypeInterfaces = new Type[0]; } //得到類型生成器 TypeBuilder typeBuilder =modBuilder.DefineType(newTypeName,newTypeAttribute,newTypeParent,newTypeInterfaces); //以下將為新類型聲明方法:新類型應該override基類型的所以virtual方法 //得到基類型的所有方法 MethodInfo[] targetMethods = targetType.GetMethods(); //遍歷各個方法,對于Virtual的方法,獲取其簽名,作為新類型的方法 foreach(MethodInfo targetMethod in targetMethods) { //只挑出virtual的方法 if(targetMethod.IsVirtual) { //得到方法的各個參數的類型 ParameterInfo[] paramInfo = targetMethod.GetParameters(); Type[] paramType = new Type[paramInfo.Length]; for(int i=0;i<paramInfo.Length;i++) paramType[i] = paramInfo[i].ParameterType; //傳入方法簽名,得到方法生成器 MethodBuilder methodBuilder = typeBuilder.DefineMethod(targetMethod.Name,MethodAttributes.Public| MethodAttributes.Virtual,targetMethod.ReturnType,paramType); //由于要生成的是具體類,所以方法的實現是必不可少的。而方法的實現是通過Emit IL代碼來產生的 //得到IL生成器 ILGenerator ilGen = methodBuilder.GetILGenerator(); //以下三行相當于:{Console.Writeln("I'm "+ targetMethod.Name +"ing");} ilGen.Emit(OpCodes.Ldstr,"I'm "+ targetMethod.Name +"ing"); ilGen.Emit(OpCodes.Call,typeof(Console).GetMethod("WriteLine",new Type[]{typeof(String)})); ilGen.Emit(OpCodes.Ret); } } //真正創建,并返回 return(typeBuilder.CreateType()); } } |
好了,測試一下試試看:
| using System; public class Tester { public static void Main(String[] args) { TypeCreator tc=new TypeCreator(typeof(IAnimal)); Type t = tc.build(); IAnimal animal= (IAnimal)Activator.CreateInstance(t); animal.move(); animal.eat(); Console.Read (); } } |
得到輸出:I'm moveingI'm eating。
總結
如果用于AOP的話,Emit可以動態生成一個裝飾類,相比于基于Remoting架構的TP/RP的方法,效率可能要高些,而且還能攔截new操作符。缺點:對于非Virtual的方法,似乎無法攔截。用于O/R Mapping的類生成,倒是不錯。