面向方面的编程、侦听和Unity 2.0

毫无疑问,面向对象是一种主流编程模式,当涉及到将某个系统分割为组件并通过组件来描述过程时,这种模式占有优势。 当处理某组件的业务特定关注点时,面向对象 (OO) 模式同样占有优势。 但是,当涉及到处理横切关注点时,OO 模式不再有效。 一般来说,横切关注点是一个在系统中影响多个组件的关注点。

为了最大限度地重用复杂的业务逻辑代码,您通常倾向于围绕系统的核心和主要业务功能设计类的层次结构。 但其他横切类层次结构的非业务特定关注点该如何实现? 缓存、安全和日志记录等功能在什么位置适合? 很可能就是在每个受影响的对象中重复使用这些功能。

横切关注点是必须在一个不同的逻辑级别(超出应用程序类范围的级别)处理的系统的一个方面,而不是给定组件或系列组件的特定职责。 出于此原因,多年前就定义了一个不同的编程模式:面向方面的编程 (AOP)。 顺便说一下,AOP 这一概念于 20 世纪 90 年代在 Xerox PARC 实验室中产生。 该团队还开发出第一种 AOP 语言(仍是最受欢迎的):AspectJ。

尽管几乎所有人都认同 AOP 的好处,但它仍未广泛实现。 在我看来,这种应用范围有限的主要原因基本上是缺乏合适的工具。 我深信,Microsoft .NET Framework 本机支持 AOP(即使只是部分支持)的那一天将成为 AOP 的历史转折点。 现在,您只能使用 ad hoc 框架在 .NET 中实现 AOP。

.NET 中 AOP 的最强大工具是 PostSharp,您可在 sharpcrafters.com 中找到。 PostSharp 提供一个完整的 AOP 框架,您可在该框架中体验 AOP 理论的所有关键功能。 但应注意,许多依赖关系注入 (DI) 框架都包括一些 AOP 功能。

例如,您会在 Spring.NET、Castle Windsor 当然还有 Microsoft Unity 中发现 AOP 功能。 对于相对简单的方案(例如,在应用层跟踪、缓存和修饰组件),DI 框架的功能通常能成功应用。 但对于域对象和 UI 对象,很难使用 DI 框架获得成功。 无疑,横切关注点会被视为外部依赖关系,而 DI 技术也必定允许您在类中注入外部依赖关系。

关键在于,DI 很可能将要求进行 ad hoc 前期设计或稍做重构。 换句话说,如果您已在使用 DI 框架,则很容易就能导入一些 AOP 功能。 反之,如果您的系统未使用 DI,则导入 DI 框架可能需要相当多的工作。 这在大型项目中或在更新旧系统的过程中并不总是可能实现的。 通过改用典型的 AOP 方法,可在一个称为“方面”的新组件中包装所有横切关注点。 在本文中,首先我将向您快速概述一下面向方面的模式,然后介绍您在 Unity 2.0 中发现的 AOP 相关功能。

AOP 快速指南

面向对象的编程 (OOP) 项目由多个源文件组成,每个源文件实现一个或多个类。 该项目还包括表示横切关注点(如日志记录或缓存)的类。 所有类均由编译器处理并生成可执行代码。 在 AOP 中,一个方面表示一个可重用的组件,它将多个类所需的行为封装在项目中。 实际处理方面的方式取决于您所考虑的 AOP 技术。 通常情况下,我们可以说各个方面并不简单直接地由编译器进行处理。 若要修改可执行代码以将方面考虑在内,需要一种额外的特定于技术的工具。 让我们大致看一下使用 AspectJ(第一个创建的 AOP 工具,即 Java AOP 编译器)会发生什么情况。

借助 AspectJ,您可使用 Java 编程语言来编写您的类,并使用 AspectJ 语言来编写方面。 AspectJ 支持自定义语法,您可通过自定义语法指示方面的预期行为。 例如,日志记录方面可能指定它将在调用特定方法之前和之后记录。 各个方面以某种方式合并到常规源代码中并产生源代码的中间版本,然后将该中间版本编译成可执行格式。 在 AspectJ 术语中,预处理方面并将方面与源代码合并的组件称为 weaver。 该组件产生一个编译器可呈现给可执行文件的输出。

总之,一个方面描述一段可重用的代码,您希望将可重用代码注入现有类中,而不接触这些类的源代码。 在其他 AOP 框架(如 .NET PostSharp 框架)中,您将找不到 weaver 工具。 但是,方面的内容始终由框架进行处理并生成某种形式的代码注入。

请注意,在这方面上,代码 注入不同于依赖关系 注入。 代码注入是指,AOP 框架能够将对方面中特定点处的公共终结点的调用插入到使用给定方面修饰的类主体中。 举例来说,PostSharp 框架让您能够将方面编写为 .NET 属性,然后将这些属性附加到类中的方法上。 PostSharp 属性由 PostSharp 编译器(我们甚至可以称之为 weaver)在生成后步骤中进行处理。 实际效果是,您的代码得到增强,从而在这些属性中包括一些代码。 但注入点将得到自动解析,您作为一名开发人员只需编写一个独立方面组件并将其附加到公共类方法即可。 代码易于编写,甚至更易于维护。

为了完成此次有关 AOP 的快速概述,我将介绍一些特定术语并解释它们各自的含义。 联接点 指示您要在目标类的源代码中注入方面代码的点。 pointcut 表示联接点集合。 建议 指的是要在目标类中注入的代码。 可在联接点的前后和四周注入代码。 一个建议与一个 pointcut 关联。 这些术语来自 AOP 的原始定义,在您使用的特定 AOP 框架中可能不会反映在字面上。 建议您尝试选取这些术语隐含的概念(AOP 的核心概念),然后使用这种知识更好地了解特定框架的详细信息。

Unity 2.0 快速指南

Unity 是作为 Microsoft Enterprise Library 项目的一部分提供的应用程序块,它也可单独下载。 Microsoft Enterprise Library 是应用程序块的集合,该集合处理大量描述 .NET 应用程序开发特征的横切关注点,如日志记录、缓存、加密、异常处理等。 Enterprise Library 的最新版本是 5.0,于 2010 年 4 月份发布,并附带对 Visual Studio 2010 的完全支持(在 msdn.microsoft.com/library/ff632023 上的“模式和实践开发人员中心”处,可了解该版本的详细信息)。

Unity 是 Enterprise Library 应用程序块之一。 Unity 同样适用于 Silverlight,它实质上是为拦截机制提供额外支持的 DI 容器,通过拦截机制可使您的类更加面向方面。

Unity 2.0 中的拦截功能

Unity 中拦截的核心理念是让开发人员能够自定义调用链,方便对对象调用方法。 也就是说,Unity 拦截机制通过在方法的常规执行前后或四周额外添加一些代码,捕获对已配置对象进行的调用并自定义目标对象的行为。 拦截实际上是在运行时向对象中添加新行为的一种极其灵活的方法,无需接触到对象的源代码,也不会影响相同继承路径中的类的行为。 Unity 拦截是实现 Decorator 模式的一种方式,该模式是一种常用设计模式,设计为在运行时扩展正在使用的对象的功能。 Decorator 是一个容器对象,它接收目标对象的实例(和维护对实例的引用),并向外界扩充其功能。

Unity 2.0 中的拦截机制同时支持实例拦截和类型拦截。 此外,不管实例化对象的方式如何,无论对象是通过 Unity 容器创建的还是一个已知实例,拦截都照常工作。 在后一种情况下,您只需使用一个不同的完全独立的 API 即可。 但是,如果您这么做,则将丢失配置文件支持。 图 1 演示 Unity 中拦截功能的体系结构,并详细说明该功能在未通过容器解析的特定对象实例上的工作方式。 (此图只是对 MSDN 文档中的某幅图稍做了一些修改。)

图 1 Unity 2.0 中对象拦截的工作方式

拦截子系统由三个关键元素组成:侦听器(或代理);行为管道;以及行为或方面。 这些子系统的两个极端分别为客户端应用程序和目标对象(即,被分配了未在其源代码中进行硬编码的其他行为的对象)。 在将客户端应用程序配置为在给定实例上使用 Unity 的拦截 API 后,所有方法调用都将通过一个代理对象(侦听器)。 此代理对象查看已注册行为的列表,并通过内部管道调用这些行为。 每个配置的行为都有机会在对象方法的常规调用之前或之后运行。 该代理将输入数据注入到管道中,然后在数据经目标对象最初生成接着由行为进一步修改后,该代理接收任何返回值。

配置拦截

在 Unity 2.0 中建议使用拦截的方法不同于早期版本,尽管在早期版本中使用的方法完全支持向后兼容性。 在 Unity 2.0 中,拦截只是您添加到容器中的一个新扩展,用来描述对象的实际解析方式。 下面是您希望通过 Fluent 代码配置拦截时所需的代码:

var container = new UnityContainer();
container.AddNewExtension<Interception>();

该容器需要查找有关要拦截的类型和要添加的行为的信息。 可使用 Fluent 代码或通过配置添加此信息。 我发现配置特别灵活,因为您无需接触应用程序也无需执行任何新的编译步骤,即可修改一些内容。 让我们采用基于配置的方法。

首先,在配置文件中添加以下内容:

<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.
Configuration.InterceptionConfigurationExtension,

Microsoft.Practices.Unity.Interception.Configuration"/>

此脚本的目的是使用特定于拦截子系统的新元素和别名来扩展配置架构。 另外,添加以下内容:

<container>
<extension type="Interception" />

<register type="IBankAccount" mapTo="BankAccount">

<interceptor type="InterfaceInterceptor" />

<interceptionBehavior type="TraceBehavior" />

</register>

</container>

若要使用 Fluent 代码实现相同的任务,您需要对容器对象调用 AddNewExtension<T> 和 RegisterType<T>。

让我们进一步看一下配置脚本。 <extension> 元素将拦截添加到容器中。 请注意,脚本中使用的“Interception”是在节扩展中定义的别名之一。 接口类型 IBankAccount 映射到具体类型 BankAccount(这是 DI 容器的典型作业),并与特定类型的侦听器相关联。 Unity 提供两种主要类型的侦听器:实例侦听器和类型侦听器。 下个月,我将深入探讨侦听器。 现在,一句话说明,实例侦听器创建一个代理来筛选针对已截获实例传入的调用。 相反,类型侦听器只是模拟已截获对象的类型,并在派生类型的实例上工作。 (有关侦听器的详细信息,请参阅 msdn.microsoft.com/library/ff660861(PandP.20)。)

接口侦听器是仅限于充当对象上一个接口的代理的实例侦听器。 接口侦听器使用动态代码生成来创建代理类。 配置中的拦截行为元素指示您要围绕已截获对象实例运行的外部代码。 必须通过声明的方式配置 TraceBehavior 类,以便容器可以解析该类及其任何依赖关系。 使用 <register> 元素告知容器 TraceBehavior 类及其所需的构造函数,如下所示:

图 2 显示 TraceBehavior 类中的一段摘录。

图 2 Unity 行为示例

class TraceBehavior : IInterceptionBehavior, IDisposable
{

private TraceSource source;

public TraceBehavior(TraceSource source)

{

if (source == null)

throw new ArgumentNullException("source");

this.source = source;

}

public IEnumerable<Type> GetRequiredInterfaces()

{

return Type.EmptyTypes;

}

public IMethodReturn Invoke(IMethodInvocation input,

GetNextInterceptionBehaviorDelegate getNext)

{

// BEFORE the target method execution

this.source.TraceInformation("Invoking {0}",

input.MethodBase.ToString());

// Yield to the next module in the pipeline

var methodReturn = getNext().Invoke(input, getNext);

// AFTER the target method execution

if (methodReturn.Exception == null)

{

this.source.TraceInformation("Successfully finished {0}",

input.MethodBase.ToString());

}

else

{

this.source.TraceInformation(

"Finished {0} with exception {1}: {2}",

input.MethodBase.ToString(),

methodReturn.Exception.GetType().Name,

methodReturn.Exception.Message);

}

this.source.Flush();

return methodReturn;

}

public bool WillExecute

{

get { return true; }

}

public void Dispose()

{

this.source.Close();

}

}

行为类实现 IinterceptionBehavior,它基本上由 Invoke 方法组成。 Invoke 方法包含您要用于受侦听器控制的任何方法的整个逻辑。 如果您想要在调用目标方法之前执行一些操作,则在该方法开头执行操作。 当您想要运行到目标对象(或者更准确的说是运行到管道中注册的下一个行为)时,需调用框架提供的 getNext 委派。 最后,您可使用任何所需的代码对目标对象进行后处理。 Invoke 方法需要返回对管道中下一个元素的引用;如果返回 Null,则链中断,后续的行为将永远不会被调用。

配置灵活性

拦截(更笼统的说是 AOP)满足了许多有用的方案的要求。 例如,利用拦截,您可向各个对象中添加责任,而无需修改整个类,并且保持解决方案相对于使用 Decorator 来说更加灵活。

本文只涉及了应用于 .NET 的 AOP 的一些皮毛。 在接下来的几个月里,我将撰写有关 Unity 和 AOP 中拦截的更多内容。

关于作者

Dino Esposito 是 Microsoft Press (2010) 出版的《Programming Microsoft ASP.NET MVC》一书的作者,并且是《Microsoft .NET:Architecting Applications for the Enterprise》(Microsoft Press,2008 年)的合著者。Esposito 定居于意大利,经常在世界各地的业内活动中发表演讲。您可访问他的博客,网址为 weblogs.asp.net/despos

原文链接:http://msdn.microsoft.com/zh-cn/magazine/gg490353.aspx

时间: 2024-09-22 01:31:32

面向方面的编程、侦听和Unity 2.0的相关文章

[WSE]Web Service—后台侦听服务通过WSE2.0建立订阅/发布关系

web|后台 由于Web Service的执行身份受限,所以我们无法直接让Web Service申请作为一个SoapReceiver,而是通过下面的web.config定义来制定本虚拟目录的.ashx终结点,从而通过WS_Addressing和WS_Messaging机制来完成与后台侦听服务之间的订阅/发布机制. <configuration> <configSections>     <section name="microsoft.web.services&qu

《WCF技术内幕》38:第2部分_第7章_通道管理器:通道侦听器

接收者:通道侦听器 和它们的名字暗示的一样,通道侦听器就是为了创建通道并侦听传入的消息 .这个模型借鉴了伯克利Socket编程API.在WCF里,这个模型可以在 Windows Socket(Winsock) API里看到.在.NET Framework编程里,这个模型存在于 System.Net.Sockets命名空间里.在这个模型里,TcpListener或Socket会绑定 一个地址,然后被动侦听连接传入的消息.当连接建立以后(例如,客户端链接 到侦听器),会有一个以Accept开头的方法

体验Java 1.5中面向方面(AOP)编程

编程 摘自久久学院对于一个能够访问源代码的经验丰富的Java开发人员来说,任何程序都可以被看作是博物馆里透明的模型.类似线程转储(dump).方法调用跟踪.断点.切面(profiling)统计表等工具可以让我们了解程序目前正在执行什么操作.刚才做了什么操作.未来将做什么操作.但是在产品环境中情况就没有那么明显了,这些工具一般是不能够使用的,或最多只能由受过训练的开发者使用.支持团队和最终用户也需要知道在某个时刻应用程序正在执行什么操作. 为了填补这个空缺,我们已经发明了一些简单的替代品,例如日志

Android Listener侦听的多种写法

Android中,View的Listener方法,在是否使用匿名类匿名对象时,有各种不 同的写法. OnClickListener和其他Listener方法一样,都是View类的接 口,重载实现后就能使用,其接口定义如下: public interface OnClickListener { /** * Called when a view has been clicked. * * @param v The view that was clicked. */ void onClick(View

Android Listener侦听的N种写法

Android中,View的Listener方法,在是否使用匿名类匿名对象时,有各种不同的写法. OnClickListener和其他Listener方法一样,都是View类的接口,重载实现后就能使用,其接口定义如下: [java] view plaincopyprint? public interface OnClickListener {       /**       * Called when a view has been clicked.       *       * @param

事件侦听器示例汇总

示例 事件的侦听器-广播器模型与事件处理函数方法不同,它允许多个代码片断互不冲突地侦听同一事件. 这么说吧,就好像订报纸,我们可以每隔5分钟去看一次报纸到没到,如果您的时间很充裕的话:也可以跟报刊店的老板打声招呼,告诉他报纸到了,就给送来.很明显,后者的效率远高于前者,在flash里面前者就像这样 this.onEnterFrame=function(){    if(paperArrived){        sendme();    }} 结果就是每一帧都得检验paperArrived是否为

用ISA 2006标准版发布Exchange 2010的OWA:创建Web侦听器

通过上面的步骤,我们完成了Exchange Server的配置工作,接下来需要配置ISA服务器,将内网的Exchange OWA发布给互联网上的用户访问. ISA是处于内网和外网之间的屏障,外网用户访问内网的Exchange时实际上访问的是ISA,所以要在ISA上导入Exchange的证书,ISA才能代表OWA站点向用户出具有效的证书.另外,在本实验拓扑中,ISA服务器处于工作组中,并不在域中,所以来自企业CA颁发的Exchange证书是不会自动被ISA信任的.解决方法就是将CA证书导入到ISA

系统日志中“Windows已经检测到一个应用程序正在侦听传入流量”

问:服务器有下面系统日志 Windows 已经检测到一个应用程序正在侦听传入流量. 名称: - 路径: C:\WINDOWS\system32\svchost.exe 进程标识符: 752 用户帐户: NETWORK SERVICE 用户域: NT AUTHORITY 服务: 是 RPC 服务器: 否 IP 版本: IPv4 IP 协议: UDP 端口号: 57214 允许的: 否 通知用户的: 否 有关更多信息,请参阅在 http://go.microsoft.com/fwlink/event

网络上的数据报侦听

#include <math.h> #include <stdio.h> #include <string.h> #include <Winsock2.h> #include <mstcpip.h> #define STATUS_FAILED 0xFFFF //定义异常出错代码 #define MAX_PACK_LEN 65535 //接收的最大IP报文 #define MAX_ADDR_LEN 16 //点分十进制地址的最大长度 #define