Unity 是一款知名的依赖注入容器( dependency injection container) ,其支持通过自定义扩展来扩充功能。
在Unity软件包内 默认包含了一个对象拦截(Interception)扩展定义。
本篇文章将介绍如何使用对象拦截(Interception)来分离横切关注点(Separation of cross-cutting concerns)。
对象拦截简介
对象拦截(Interception)是一种 AOP(Aspect-oriented programming)编程的实践方法。
其可帮助你保持业务类的纯净,而无需考虑诸如日志和缓存等外围关注点(other concerns)。
在.NET中,实现AOP有多种方法。
一种方式是采用编译后( post-compiler)处理方式,例如 PostSharp。在编译后( after compilation),PostSharp通过修改IL代码来注入(injects)横切代码。
相反地,对象拦截(Interception)是在运行时(Runtime)执行的,同时也意味着会有一些限制。依据不同的拦截器实现,会有如下这些约束:
- 透明代理拦截器(Transparent Proxy Interceptor):需要定义接口(interface),或者要求类继承(inherit)自MarshalByRefObject。
- 接口拦截器(Interface Interceptor):需要定义接口。
- 虚方法拦截器(Virtual Method Interceptor):仅需要方法被定义成virtual方法。
对象拦截如何工作
当从Unity容器请求目标对象时,将不会获取到已配置的类的实例。
实际上,将得到一个动态生成的代理对象(dynamically generated proxy object),或者一个衍生类(derived class)。
如果调用代理对象的一个方法,将可以在被调用方法执行前或执行后执行一些额外行为的代码。
那些定义行为的类需要实现ICallHandler接口。通过这些行为定义,我们可以访问方法调用的参数列表,可以吞噬异常,或者可以返回自定义的异常。
附带提一下,在不使用Unity容器的条件下,也是可以使用Unity拦截器的。
示例:将异常和方法调用参数列表记录到日志中
在下面的示例中,我们将创建两个自定义的行为,都实现了ICallHandler接口:
- LoggerCallHandler:将方法调用参数列表记录到日志中。
- ExceptionLoggerCallHandler:将方法调用参数列表和异常信息及调用栈记录到日志中。
ExceptionLoggerCallHandler定义如下:
internal class ExceptionLoggerCallHandler : ICallHandler { public IMethodReturn Invoke( IMethodInvocation input, GetNextHandlerDelegate getNext) { IMethodReturn result = getNext()(input, getNext); if (result.Exception != null) { Console.WriteLine("ExceptionLoggerCallHandler:"); Console.WriteLine("\tParameters:"); for (int i = 0; i < input.Arguments.Count; i++) { var parameter = input.Arguments[i]; Console.WriteLine( string.Format("\t\tParam{0} -> {1}", i, parameter.ToString())); } Console.WriteLine(); Console.WriteLine("Exception occured: "); Console.WriteLine( string.Format("\tException -> {0}", result.Exception.Message)); Console.WriteLine(); Console.WriteLine("StackTrace:"); Console.WriteLine(Environment.StackTrace); } return result; } public int Order { get; set; } }
为了将行为应用到方法上,我们需要创建相应的HandlerAttribute来创建行为的实例。
internal class ExceptionLoggerAttribute : HandlerAttribute { public override ICallHandler CreateHandler(IUnityContainer container) { return new ExceptionLoggerCallHandler(); } }
在这个示例中,我们创建一个简单的计算器类。
同时为了使用接口拦截功能,我们还需创建一个接口类型,这样才能应用指定的行为:
public interface ICalculator { [Logger] int Add(int first, int second); [ExceptionLogger] int Multiply(int first, int second); }
计算器类的实现还和常规的一样。
现在我们需要配置Unity容器:
IUnityContainer container = new UnityContainer(); container.AddNewExtension<Interception>(); container .RegisterType<ICalculator, Calculator>() .Configure<Interception>() .SetInterceptorFor<ICalculator>(new InterfaceInterceptor()); // Resolve ICalculator calc = container.Resolve<ICalculator>(); // Call method calc.Add(1, 2);
当在容器上通过调用Resolve方法来尝试获得一个Calculate类的实例时,将会得到一个代理类实例。
它的名字可能类似于 ‘DynamicModule.ns.Wrapped_ICalculator_83093f794c8d452e8af4524873a017de’。
当调用此包装类的某个方法时,CallHandlers将会被执行。
总结
Unity并不提供一个完整的AOP框架,因此使用它会有一些限制。但不管怎样,使用Unity对象拦截功能来实现一些基本的AOP需求已经足够了。
原文链接:AOP - Interception with Unity 2.0 (http://www.palmmedia.de/Blog/2010/9/26/aop-interception-with-unity-20)