(转自MSDN杂志:http://msdn.microsoft.com/zh-cn/magazine/gg598927.aspx)javascript
在以前两篇文章中,我介绍了使用 Microsoft Unity 2.0 进行的面向方面的编程 (AOP)。AOP 成型于二十世纪九十年代,是为进一步改进和补充面向对象编程 (OOP) 而开发的编程技术,这种编程技术最近有所更新,而且获得许多控制反转 (IoC) 库的支持。 Unity 也不例外。 AOP 的主要目的是让开发人员更加有效地处理横切关注点。 AOP 在本质上是解决了下面的问题:在为某个应用程序设计对象模型的时候,您如何处理代码的安全、缓存或日志记录等方面? 这些方面对于实现十分重要,但并不严格属于您所构建的模型中的对象。 是否应在设计中大肆归入业务以外的方面? 或者,利用其余方面来修饰面向业务的类是否会更好? 若是您选择后者,AOP 基本上能够提供相关语法来定义和附加这些方面。java
所谓“方面”,就是横切关注点的实现。 在方面的定义中,您须要指定一些事情。 首先,您须要为所实现的关注点提供代码。 在 AOP 术语中,这称为“建议”。 建议应用于某个特定的代码点,该代码点能够是方法主体、属性的 getter/setter,或者也多是异常处理程序。 这个代码点称为“联接点”。 最后,在 AOP 术语中,还有一个称做“切入点”。 切入点是联接点的集合。 一般,切入点经过方法名称和通配符由条件进行定义。 最终,AOP 在运行时在联接点先后注入建议代码。 这样一个建议即与一个切入点创建关联。web
在以前的文章中,我讨论了 Unity 的拦截 API。 借助拦截 API 能够定义附加到类上的建议。 在 Unity 术语中,建议是一个行为对象。 一般,行为附加到经过 Unity 的 IoC 机制进行解析的某个类型上,不过拦截机制并不严格要求使用 IoC 功能。 实际上,您能够配置拦截,使之一样应用于经过纯代码建立的实例。编程
行为经过一个实现固定接口(IInterceptionBehavior 接口)的类来体现。 该接口有一个名为 Invoke 的方法。经过重写此方法,您实际上定义了要在常规方法调用以前和/或以后所执行的步骤。 除了配置脚本,您也可使用 Fluent 代码将行为附加到某个类型。 这样,您所要作的只是定义一个联接点。 那切入点呢?缓存
咱们上个月讨论过,目标对象上全部被拦截的方法都依照行为对象的 Invoke 方法中所表达的逻辑执行。 基本拦截 API 不能区分不一样方法,而且不支持特定的匹配规则。 要作到这一点,您能够求助于策略注入 API。安全
若是您用过 Microsoft Enterprise Library (EntLib) 最新版本 5.0 以前的版本,那么您有可能据说过 Policy Injection Application Block (PIAB),而且您还可能在本身的某些应用程序中用过 PIAB。 EntLib 5.0 也有 PIAB 模块。 那么 Unity 策略注入与 EntLib PIAB 之间的区别何在?app
在 EntLib 5.0 中,PIAB 主要出于兼容方面的缘由而存在。 在新版本中,PIAB 程序集的内容发生了变化。 具体而言,拦截的全部功能组件如今都已成为 Unity 的组成部分,而且先前 EntLib 版本中的全部系统提供的调用处理程序都已转移至其余程序集,如图 1 所示。框架
图 1 Microsoft Enterprise Library 5.0 中对调用处理程序的重构ide
调用处理程序 | Enterprise Library 5.0 中的新程序集 |
受权处理程序 | Microsoft.Practices.EnterpriseLibrary.Security.dll |
缓存处理的处理程序 | 从 PIAB 中删除 |
异常处理的处理程序 | Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll |
日志记录处理程序 | Microsoft.Practices.EnterpriseLibrary.Logging.dll |
性能计数器处理程序 | Microsoft.Practices.EnterpriseLibrary.PolicyInjection.dll |
验证处理程序 | Microsoft.Practices.EnterpriseLibrary.Validation.dll |
如图 1 所示,每一个调用处理程序都已移相当联应用程序块的程序集。 所以,异常处理调用处理程序移至异常处理应用程序块,而验证处理程序移至验证应用程序块,依此类推。 其中惟一的例外是性能计数器处理程序,它移入了 PolicyInjection 程序集。 尽管程序集发生了变化,但类的命名空间仍然保持不变。 另外值得注意的是,出于安全方面的缘由,以前位于 PIAB 中的缓存调用处理程序已从 EntLib 5.0 中删除,而只能从 EntLib Contrib CodePlex 网站得到:bit.ly/gIcP6H。 这些变化的影响是 PIAB 如今由旧的组件组成,这些组件只是为了向后兼容而存在,而且仍需一些代码变动才能与版本 5.0 兼容。 除非您对旧版本依赖严重,不然建议您升级策略注入层,以便利用已融入 Unity 应用程序块的新的(且大致相似的)策略注入 API。 让咱们深刻探讨一下 Unity 中的策略注入。性能
策略注入指的是一层代码,它扩展基本 Unity 拦截 API,以针对每一个方法添加映射规则和调用处理程序。 策略注入实现为一种特别的拦截行为,分两个主要阶段:初始化和执行时。
在初始化阶段,框架首先肯定哪一个可用策略能够应用于所拦截的目标方法。 在这里,策略是一组操做,能够按照特定顺序注入到所拦截的对象与它的实际调用方之间。 您只能拦截针对策略注入显式配置的对象上的方法(现有实例或新建的实例都可)。
肯定适用策略列表以后,策略注入框架开始准备操做管道(操做称为调用处理程序)。 管道经过组合为每一个匹配策略而定义的全部处理程序而造成。 管道中的处理程序根据策略顺序进行排序,而且父策略中的每一个处理程序都分配有优先级。 在调用某个启用策略的方法时,将处理以前构建的管道。 若是该方法转而调用同一对象上其余启用策略的方法,这些方法的处理程序管道将合并到主管道中。
调用处理程序比“行为”更加具体,而且由于最初在 AOP 中定义而看起来与建议十分类似。 行为应用于类型,由您负责为不一样的方法采起不一样的操做,而调用处理程序则针对每一个方法进行指定。
调用处理程序造成于管道中,并按预先肯定的顺序接受调用。 每一个处理程序可以访问调用的详细信息,包括方法名称、参数、返回值和预期返回类型。 调用处理程序还能够修改参数和返回值、中止调用在管道中的传播以及引起异常。
值得注意的是,Unity 并不附带提供任何调用处理程序。 您只能本身建立调用处理程序,或从 EntLib 5.0 引用应用程序块并使用图 1 中列出的任何调用处理程序。
调用处理程序是一个实现 ICallHandler 接口的类,例如:
Order 属性表示此处理程序相对于全部其余处理程序的优先级。 Invoke 方法返回一个包含该方法的任何返回值的类实例。
调用处理程序只是执行本身的具体工做,而后释放管道,在这个意义上,调用处理程序的实现十分简单。 为了将控制权转交给管道中的下一个处理程序,处理程序会调用从 Unity 运行时收到的 getNext 参数。getNext 参数是一个委托,定义为:
而 InvokeHandlerDelegate 定义为:
Unity 文档提供了一个阐述拦截的清晰图表。 在图 2 中,您能够看到一个略加修改的图表,它表示了策略注入的体系结构。
图 2 Unity 策略注入中的调用处理程序管道
在系统提供的策略注入行为范围以内,您能够看处处理程序链用以处理对代理对象或派生类所调用的某个给定方法。 为了完整概述 Unity 中的策略注入,咱们须要了解一下匹配规则。
经过匹配规则,您能够指定在哪里应用拦截逻辑。 若是您使用了行为,您的代码将会应用于整个对象;利用一条或更多匹配规则能够定义筛选器。 匹配规则表示用以选择特定对象和成员的条件,Unity 将为这些对象和成员附加处理程序管道。 用 AOP 术语来讲,匹配规则就是用以定义切入点的条件。 图 3 列出了 Unity 提供本机支持的匹配规则。
图 3 Unity 2.0 中受支持匹配规则的列表
匹配规则 | 说明 |
AssemblyMatchingRule | 基于指定程序集中的类型选择目标对象。 |
CustomAttributeMatchingRule | 基于成员级别的自定义属性选择目标对象。 |
MemberNameMatchingRule | 基于成员名称选择目标对象。 |
MethodSignatureMatchingRule | 基于签名选择目标对象。 |
NamespaceMatchingRule | 基于命名空间选择目标对象。 |
ParameterTypeMatchingRule | 基于某个成员的某个参数的类型名称选择目标对象。 |
PropertyMatchingRule | 基于成员名称(包括通配符)选择目标对象。 |
ReturnTypeMatchingRule | 基于返回类型选择目标对象。 |
TagMatchingRule | 基于临时 Tag 属性的赋值选择目标对象。 |
TypeMatchingRule | 基于类型名称选择目标对象。 |
匹配规则是实现 IMatchingRule 接口的类。 了解这一点以后,让咱们来看看如何使用策略注入。 定义策略主要有三种方式:使用属性,使用 Fluent 代码,以及经过配置。
图 4 是一个示例调用处理程序,它在某个操做获得负值结果时引起异常。 我会在不一样状况下使用这个处理程序。
图 4 NonNegativeCallHandler 类
使用处理程序最为简单的方式是在您所认为它能够发挥做用的位置将之附加至方法。 为此,您须要一个属性,例如:
下面是一个示例 Calculator 类,为之附加了基于属性的策略:
其结果是不论返回值是什么,对于方法 Sum 的调用都会照常执行,而若有负数返回,对于方法 Sub 的调用则会引起异常。
若是您不喜欢属性,也能够经过 Fluent API 来表达相同的逻辑。 这种状况下,在匹配规则方面须提供更多详细信息。 咱们来看看如何表达这样一种想法:只在返回 Int32 而且名为 Sub 方法中注入代码。 咱们使用 Fluent API 来配置 Unity 容器(参见图 5)。
图 5 用以定义一组匹配规则的 Fluent 代码
请注意,若是您使用 ContainerControlledLifetimeManager 管理器,则全部方法一定会共享同一个调用处理程序实例。
这段代码的效果是任何实现 ICalculator 的具体类型(即配置为接受拦截并经过 Unity 进行解析)都会选择两个潜在的注入候选对象:Sub 方法和 Test 方法。 然而,只有返回类型为 Int32 的方法才会继续匹配其余匹配规则。 这就是说,若是 Test 返回双精度值,它便会遭到排除。
最后,能够经过配置文件表达一样的理念。 图 6 是 <unity> 节的预期内容。
图 6 在配置文件中准备策略注入
结果代表,若是在一个策略中有多个匹配规则,最终结果是对全部规则应用布尔运算符 AND(也就是说,全都必须为真)。 若是定义了多个策略,对于每一个策略都会进行独立的匹配评估和处理程序应用。 于是,您能够从不一样的策略应用处理程序。
总结一下,拦截是 Microsoft .NET Framework 空间中大多数 IoC 框架实现面向方面编程的一种方式。 经过拦截,您能够在任何特定程序集中任何特定类型的任何特定方法先后运行本身的代码。 之前,EntLib 提供了特定的应用程序块 PIAB 来执行这一工做。 在 EntLib 5.0 中,PIAB 的底层引擎已经转入 Unity,而且实现为 Unity 低级拦截 API 的一种特别行为(对于这种 API 在以前两期专栏中已有讨论)。 策略注入行为要求使用 Unity 容器,而且不只限于经过低级拦截 API 使用。
然而,低级拦截 API 不能用以选择您要拦截的类型成员,您必须本身编写代码来执行这一工做。 但利用策略注入行为,您能够将精力集中于所需行为的细节上面,而让库根据您所提供的规则来肯定行为应用于哪些方法。