摘要
使用 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().RegisterType ();//声明UnityContainer并注册IOutput container1.Configure ().SetInterceptorFor (new InterfaceInterceptor()); IOutput op1 = container1.Resolve (); 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 }}
#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”这样的类了。 版权声明:本文原创发表于 ,作者为 ,博客 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
顺便推荐2篇博客: