Unity AOP 处理异常的方法
生活随笔
收集整理的這篇文章主要介紹了
Unity AOP 处理异常的方法
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
摘要
使用 unity 處理異常的方法可能會與你的直覺不符。本文將給出正確的處理方法,并簡單剖析Unity這部分源代碼。
處理異常
打算用Unity的AOP截獲未處理的異常,然后寫個日志什么的,于是我寫下了這樣的代碼(注意 這段代碼是錯誤的):
public?class?MyHandler?:?ICallHandler
{
????public?int?Order?{?get;?set;?}//?這是ICallHandler的成員,表示執行順序???
????public?IMethodReturn?Invoke(IMethodInvocation?input,?GetNextHandlerDelegate?getNext)
????{
????????//這之前插入方法執行前的處理??
????????Console.WriteLine("執行前");
????????IMethodReturn?retvalue?=?null;
????????try
????????{
????????????retvalue?=?getNext()(input,?getNext);//?在這里執行方法??
????????}
????????catch?(Exception?ex)
????????{
????????????Console.WriteLine("ExMsg:"?+?ex.Message);?//?處理異常,例如寫日志之類的。
????????}
????????//這之后插入方法執行后的處理??
????????Console.WriteLine("完成");
????????return?retvalue;
????}
}為了測試一下異常有沒有被成功截獲,我讓被調用的函數拋出一個異常:
[MyHandler]
public?class?OutputImplement1?:?IOutput
{
????public?void?Output(int?x)
????{
????????Console.WriteLine("執行此方法輸出:{0}",?x);
????????throw?new?Exception("這里拋個異常出來");
????}
} 出人意料的是,異常并沒有被我的Catch截獲。單步執行,可以發現在執行try區塊里面的代碼時確實有異常拋出,只是catch區塊里面的代碼根本沒有執行,然后控制臺就顯示有未處理的異常了。其實,第一段代碼是錯誤的,正確的代碼應該像這樣:
public?class?MyHandler?:?ICallHandler
{
????public?int?Order?{?get;?set;?}//這是ICallHandler的成員,表示執行順序???
????public?IMethodReturn?Invoke(IMethodInvocation?input,?GetNextHandlerDelegate?getNext)
????{
????????//這之前插入方法執行前的處理??
????????Console.WriteLine("執行前");
????????IMethodReturn?retvalue?=?getNext()(input,?getNext);//在這里執行方法??
????????if?(retvalue.Exception?==?null)?//?retvalue.Exception=null說明函數執行時沒有拋出異常
????????{
????????????Console.WriteLine("執行成功,無異常");
????????}
????????else
????????{
????????????Console.WriteLine("Exxxxxx:"?+?retvalue.Exception.Message);
????????????retvalue.Exception?=?null;?//?將retvalue.Exception設為null表示異常已經被處理過了,
???????????????????????????????????????//?如果不把retvalue.Exception設為null,Unity會再次拋出此異常。
????????}
????????//這之后插入方法執行后的處理??
????????Console.WriteLine("完成");
????????return?retvalue;
????}
}
原因
我百思不得其解:既然在try里面已經執行了OutputImplement1.Output()函數,也拋出了異常,為什么catch區塊內的代碼卻沒有被執行呢?好在Untity是開源的,可以深入源碼一探究竟。
找到原因后,稍稍有些失望,因為這原因說起來平平無奇:因為在我try之前Unity已經try完了。也就是說,第一段代碼執行起來會像這樣(偽代碼,加粗部分是Unity動態生成的):
public?class?MyHandler?:?ICallHandler
{
????public?int?Order?{?get;?set;?}//?這是ICallHandler的成員,表示執行順序???
????public?IMethodReturn?Invoke(IMethodInvocation?input,?GetNextHandlerDelegate?getNext)
????{
????????//這之前插入方法執行前的處理??
????????Console.WriteLine("執行前");
????????IMethodReturn?retvalue?=?null;
????????try
????????{
????????????try
????????????{
????????????????IParameterCollection?arguments?=?inputs.Arguments;
????????????????this.target.Output((int)?arguments[0]);?//?在這里執行方法,并拋出異常
????????????????retvalue?=?inputs.CreateMethodReturn(null,?new?object[]?{?arguments[0]?});
????????????}
????????????catch?(Exception?exception)
????????????{
????????????????retvalue?=?inputs.CreateExceptionMethodReturn(exception);
????????????}
????????}
????????catch?(Exception?ex)
????????{
????????????Console.WriteLine("ExMsg:"?+?ex.Message);?//?處理異常,例如寫日志之類的。
????????}
????????//這之后插入方法執行后的處理??
????????Console.WriteLine("完成");
????????return?retvalue;
????}
} 當然,上面這段代碼為了說明問題作了改寫和簡化,如果對真實的代碼感興趣可以接著往下看。
源碼剖析
類圖:
藍色背景的類是我寫的演示程序中的代碼,是照著重典的教程做的。IOutPut 和 OutputImplement1相當于業務代碼的接口和實現。MyHandler用來截獲對OutputImplement1里的函數的調用:
Unity AOP 演示代碼
class?Program
{
????static?void?Main(string[]?args)
????{
????????var?container1?=?new?UnityContainer().AddNewExtension<Interception>().RegisterType<IOutput,?OutputImplement1>();//聲明UnityContainer并注冊IOutput???
????????container1.Configure<Interception>().SetInterceptorFor<IOutput>(new?InterfaceInterceptor());
????????IOutput?op1?=?container1.Resolve<IOutput>();
????????op1.Output(11);//調用
????????Console.ReadLine();
????}
}
public?interface?IOutput
{
????void?Output(int?x);
}
[MyHandler]
public?class?OutputImplement1?:?IOutput
{
????public?void?Output(int?x)
????{
????????Console.WriteLine("執行此方法輸出:{0}",?x);
????????throw?new?Exception("這里拋個異常出來");
????}
}
public?class?MyHandler?:?ICallHandler
{
????public?int?Order?{?get;?set;?}//這是ICallHandler的成員,表示執行順序???
????public?IMethodReturn?Invoke(IMethodInvocation?input,?GetNextHandlerDelegate?getNext)
????{
????????//這之前插入方法執行前的處理??
????????Console.WriteLine("執行前");
????????IMethodReturn?retvalue?=?getNext()(input,?getNext);//在這里執行方法??
????????if?(retvalue.Exception?==?null)?//?retvalue.Exception=null說明函數執行時沒有拋出異常
????????{
????????????Console.WriteLine("執行成功,無異常");
????????}
????????else
????????{
????????????Console.WriteLine("Exxxxxx:"?+?retvalue.Exception.Message);
????????????retvalue.Exception?=?null;?//?將retvalue.Exception設為null表示異常已經被處理過了,
???????????????????????????????????????//?如果不把retvalue.Exception設為null,Unity會再次拋出此異常。
????????}
????????//這之后插入方法執行后的處理??
????????Console.WriteLine("完成");
????????return?retvalue;
????}
}
public?class?MyHandlerAttribute?:?HandlerAttribute
{
????public?override?ICallHandler?CreateHandler(IUnityContainer?container)
????{
????????return?new?MyHandler();//返回MyHandler???
????}
}
淺綠色背景的類“Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc”是由Unity使用Emit在運行期動態創建的包裝類。它實現IOutput接口,隱式實現IInterceptingProxy接口。Main()函數中的“IOutput op1 = container1.Resolve<IOutput>();”實際返回的就是這個類對象。所以下一句“op1.Output(11);”執行的是Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc.Output(11)。這個函數首先將被調用函數“op1.Output(11)”的元數據(函數名、調用對象、參數等等)封裝到一個VirtualMethodInvocation對象中,然后把它作為參數傳遞給pipeline.Invoke()。pipeline.Invoke()遞歸調用所有的攔截器(即ICallHandler的實現類,本例中是MyHandler)的Invoke()函數。
對OutputImplement1.Output()的調用封裝在了Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc.<Output_DelegateImplementation>__0()函數中。“<Output_DelegateImplementation>__0”這個函數名看上去有些怪,那兩個“<>”符號很容易讓人感覺是泛型,但其實這就是個比較怪的函數名而已。
附錄 如何取得動態生成的類的代碼?
Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc這個類是Unity使用Emit動態創建的,所以無論看Unity的源代碼或是使用Reflector都無法取得它的源代碼。要想看它的源代碼,有兩種方法。
第一種方法是使用Reflector的能加載運行著的進程的插件。但是實際上我嘗試了N多次也沒成功,總是剛剛執行了Main()函數的第一行就報錯。
第二種方法是直接用Emit的AssemblyBuilder.Save()函數將動態生成的程序集保存到硬盤上。事實上,Unity的開發人員已經把這個代碼寫好了,就在“Microsoft Unity Application Block 1.2\UnitySource\UnitySource\Src\Unity.Interception\Interceptors\InstanceInterceptors\InterfaceInterception\InterfaceInterceptorClassGenerator.cs”的第79行:
#if?DEBUG_SAVE_GENERATED_ASSEMBLY
????assemblyBuilder.Save("Unity_ILEmit_InterfaceProxies.dll");
#endif我們所需要做的就是在此文件的第一行添加一句“#define DEBUG_SAVE_GENERATED_ASSEMBLY”,讓這段代碼執行就行了。記得要重新編譯,然后讓我們的演示程序引用修改后生成的那個Microsoft.Practices.Unity.Interception.dll。再次運行我們的演示程序,就可以在它的bin\Debug\找到名為“Unity_ILEmit_InterfaceProxies.dll”的程序集,用Reflector打開它就可以找到
類似于“Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc”這樣的類了。
使用 unity 處理異常的方法可能會與你的直覺不符。本文將給出正確的處理方法,并簡單剖析Unity這部分源代碼。
處理異常
打算用Unity的AOP截獲未處理的異常,然后寫個日志什么的,于是我寫下了這樣的代碼(注意 這段代碼是錯誤的):
public?class?MyHandler?:?ICallHandler
{
????public?int?Order?{?get;?set;?}//?這是ICallHandler的成員,表示執行順序???
????public?IMethodReturn?Invoke(IMethodInvocation?input,?GetNextHandlerDelegate?getNext)
????{
????????//這之前插入方法執行前的處理??
????????Console.WriteLine("執行前");
????????IMethodReturn?retvalue?=?null;
????????try
????????{
????????????retvalue?=?getNext()(input,?getNext);//?在這里執行方法??
????????}
????????catch?(Exception?ex)
????????{
????????????Console.WriteLine("ExMsg:"?+?ex.Message);?//?處理異常,例如寫日志之類的。
????????}
????????//這之后插入方法執行后的處理??
????????Console.WriteLine("完成");
????????return?retvalue;
????}
}為了測試一下異常有沒有被成功截獲,我讓被調用的函數拋出一個異常:
[MyHandler]
public?class?OutputImplement1?:?IOutput
{
????public?void?Output(int?x)
????{
????????Console.WriteLine("執行此方法輸出:{0}",?x);
????????throw?new?Exception("這里拋個異常出來");
????}
} 出人意料的是,異常并沒有被我的Catch截獲。單步執行,可以發現在執行try區塊里面的代碼時確實有異常拋出,只是catch區塊里面的代碼根本沒有執行,然后控制臺就顯示有未處理的異常了。其實,第一段代碼是錯誤的,正確的代碼應該像這樣:
public?class?MyHandler?:?ICallHandler
{
????public?int?Order?{?get;?set;?}//這是ICallHandler的成員,表示執行順序???
????public?IMethodReturn?Invoke(IMethodInvocation?input,?GetNextHandlerDelegate?getNext)
????{
????????//這之前插入方法執行前的處理??
????????Console.WriteLine("執行前");
????????IMethodReturn?retvalue?=?getNext()(input,?getNext);//在這里執行方法??
????????if?(retvalue.Exception?==?null)?//?retvalue.Exception=null說明函數執行時沒有拋出異常
????????{
????????????Console.WriteLine("執行成功,無異常");
????????}
????????else
????????{
????????????Console.WriteLine("Exxxxxx:"?+?retvalue.Exception.Message);
????????????retvalue.Exception?=?null;?//?將retvalue.Exception設為null表示異常已經被處理過了,
???????????????????????????????????????//?如果不把retvalue.Exception設為null,Unity會再次拋出此異常。
????????}
????????//這之后插入方法執行后的處理??
????????Console.WriteLine("完成");
????????return?retvalue;
????}
}
原因
我百思不得其解:既然在try里面已經執行了OutputImplement1.Output()函數,也拋出了異常,為什么catch區塊內的代碼卻沒有被執行呢?好在Untity是開源的,可以深入源碼一探究竟。
找到原因后,稍稍有些失望,因為這原因說起來平平無奇:因為在我try之前Unity已經try完了。也就是說,第一段代碼執行起來會像這樣(偽代碼,加粗部分是Unity動態生成的):
public?class?MyHandler?:?ICallHandler
{
????public?int?Order?{?get;?set;?}//?這是ICallHandler的成員,表示執行順序???
????public?IMethodReturn?Invoke(IMethodInvocation?input,?GetNextHandlerDelegate?getNext)
????{
????????//這之前插入方法執行前的處理??
????????Console.WriteLine("執行前");
????????IMethodReturn?retvalue?=?null;
????????try
????????{
????????????try
????????????{
????????????????IParameterCollection?arguments?=?inputs.Arguments;
????????????????this.target.Output((int)?arguments[0]);?//?在這里執行方法,并拋出異常
????????????????retvalue?=?inputs.CreateMethodReturn(null,?new?object[]?{?arguments[0]?});
????????????}
????????????catch?(Exception?exception)
????????????{
????????????????retvalue?=?inputs.CreateExceptionMethodReturn(exception);
????????????}
????????}
????????catch?(Exception?ex)
????????{
????????????Console.WriteLine("ExMsg:"?+?ex.Message);?//?處理異常,例如寫日志之類的。
????????}
????????//這之后插入方法執行后的處理??
????????Console.WriteLine("完成");
????????return?retvalue;
????}
} 當然,上面這段代碼為了說明問題作了改寫和簡化,如果對真實的代碼感興趣可以接著往下看。
源碼剖析
類圖:
藍色背景的類是我寫的演示程序中的代碼,是照著重典的教程做的。IOutPut 和 OutputImplement1相當于業務代碼的接口和實現。MyHandler用來截獲對OutputImplement1里的函數的調用:
Unity AOP 演示代碼
class?Program
{
????static?void?Main(string[]?args)
????{
????????var?container1?=?new?UnityContainer().AddNewExtension<Interception>().RegisterType<IOutput,?OutputImplement1>();//聲明UnityContainer并注冊IOutput???
????????container1.Configure<Interception>().SetInterceptorFor<IOutput>(new?InterfaceInterceptor());
????????IOutput?op1?=?container1.Resolve<IOutput>();
????????op1.Output(11);//調用
????????Console.ReadLine();
????}
}
public?interface?IOutput
{
????void?Output(int?x);
}
[MyHandler]
public?class?OutputImplement1?:?IOutput
{
????public?void?Output(int?x)
????{
????????Console.WriteLine("執行此方法輸出:{0}",?x);
????????throw?new?Exception("這里拋個異常出來");
????}
}
public?class?MyHandler?:?ICallHandler
{
????public?int?Order?{?get;?set;?}//這是ICallHandler的成員,表示執行順序???
????public?IMethodReturn?Invoke(IMethodInvocation?input,?GetNextHandlerDelegate?getNext)
????{
????????//這之前插入方法執行前的處理??
????????Console.WriteLine("執行前");
????????IMethodReturn?retvalue?=?getNext()(input,?getNext);//在這里執行方法??
????????if?(retvalue.Exception?==?null)?//?retvalue.Exception=null說明函數執行時沒有拋出異常
????????{
????????????Console.WriteLine("執行成功,無異常");
????????}
????????else
????????{
????????????Console.WriteLine("Exxxxxx:"?+?retvalue.Exception.Message);
????????????retvalue.Exception?=?null;?//?將retvalue.Exception設為null表示異常已經被處理過了,
???????????????????????????????????????//?如果不把retvalue.Exception設為null,Unity會再次拋出此異常。
????????}
????????//這之后插入方法執行后的處理??
????????Console.WriteLine("完成");
????????return?retvalue;
????}
}
public?class?MyHandlerAttribute?:?HandlerAttribute
{
????public?override?ICallHandler?CreateHandler(IUnityContainer?container)
????{
????????return?new?MyHandler();//返回MyHandler???
????}
}
淺綠色背景的類“Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc”是由Unity使用Emit在運行期動態創建的包裝類。它實現IOutput接口,隱式實現IInterceptingProxy接口。Main()函數中的“IOutput op1 = container1.Resolve<IOutput>();”實際返回的就是這個類對象。所以下一句“op1.Output(11);”執行的是Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc.Output(11)。這個函數首先將被調用函數“op1.Output(11)”的元數據(函數名、調用對象、參數等等)封裝到一個VirtualMethodInvocation對象中,然后把它作為參數傳遞給pipeline.Invoke()。pipeline.Invoke()遞歸調用所有的攔截器(即ICallHandler的實現類,本例中是MyHandler)的Invoke()函數。
對OutputImplement1.Output()的調用封裝在了Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc.<Output_DelegateImplementation>__0()函數中。“<Output_DelegateImplementation>__0”這個函數名看上去有些怪,那兩個“<>”符號很容易讓人感覺是泛型,但其實這就是個比較怪的函數名而已。
附錄 如何取得動態生成的類的代碼?
Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc這個類是Unity使用Emit動態創建的,所以無論看Unity的源代碼或是使用Reflector都無法取得它的源代碼。要想看它的源代碼,有兩種方法。
第一種方法是使用Reflector的能加載運行著的進程的插件。但是實際上我嘗試了N多次也沒成功,總是剛剛執行了Main()函數的第一行就報錯。
第二種方法是直接用Emit的AssemblyBuilder.Save()函數將動態生成的程序集保存到硬盤上。事實上,Unity的開發人員已經把這個代碼寫好了,就在“Microsoft Unity Application Block 1.2\UnitySource\UnitySource\Src\Unity.Interception\Interceptors\InstanceInterceptors\InterfaceInterception\InterfaceInterceptorClassGenerator.cs”的第79行:
#if?DEBUG_SAVE_GENERATED_ASSEMBLY
????assemblyBuilder.Save("Unity_ILEmit_InterfaceProxies.dll");
#endif我們所需要做的就是在此文件的第一行添加一句“#define DEBUG_SAVE_GENERATED_ASSEMBLY”,讓這段代碼執行就行了。記得要重新編譯,然后讓我們的演示程序引用修改后生成的那個Microsoft.Practices.Unity.Interception.dll。再次運行我們的演示程序,就可以在它的bin\Debug\找到名為“Unity_ILEmit_InterfaceProxies.dll”的程序集,用Reflector打開它就可以找到
類似于“Wrapped_IOutput_f56a10859ef14d7794f3aa40d4eb36cc”這樣的類了。
總結
以上是生活随笔為你收集整理的Unity AOP 处理异常的方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHP serialize JSON
- 下一篇: Visual Studio 2010 U