您的位置:首页 > 运维架构

.Net平台AOP技术研究

2010-07-05 10:19 288 查看
.Net平台与Java平台相比,由于它至今在服务端仍不具备与unix系统的兼容性,也不具备类似于Java平台下J2EE这样的企业级容器,使得.Net平台在大型的企业级应用上,常常为人所诟病。就目前而言,.Net平台并没有提供AOP技术的直接实现,而微软在未来对于.Net的发展战略目标,我们仍未可知。但我相信微软对于目前炙手可热的AOP技术应该不会视而不见。也许在未来的.Net平台下,会出现类似于Spring那样的轻量级IoC容器,加上O/R Mapping的进一步实现与完善,随着Windows Server操作系统的逐步推新,.Net平台对于企业级系统开发的支持会越来越多。
AOP技术在.Net平台中的应用,相较于Java平台而言,还远不够成熟,功能也相对较弱,目前能够投入商用的AOP工具几乎没有。借鉴Java开源社区的成功,.Net平台下AOP工具的开发也都依托于开源社区的力量。众多开源爱好者,仍然在坚持不懈对AOP技术进行研究和实践,试图找到AOP技术与.Net之间的完美结合点,从而开发出真正能够商用的功能强大的AOP工具。就目前而言,大部分在.Net平台下的AOP工具,大部分均脱胎于Java平台下的AOP工具,例如Spring.Net之于Spring,Eos之于AspectJ。由于Java平台和.Net平台在语言机制上的相似性,使得它们在实现AOP的技术机制上,大体相似,无非是利用静态织入或动态织入的方式,完成对aspect的实现。

目前在.Net平台下的AOP大部分仍然处于最初的开发阶段,各自发布的版本基本都是beta版。其中较有代表性的AOP工具包括Aspect#,Spring.Net,Eos等。

Aspect#是基于Castle动态代理技术实现的。Castle动态代理技术利用了.Net的Emit技术,生成一个新的类去实现特定的接口,或者扩展一个已有的类,并将其委托指向IInterceptor接口的实现类。通过Castle动态代理技术,就可以拦截方法的调用,并将Aspect的业务逻辑织入到方法中。利用Castle动态代理技术,最大的缺陷是它只对虚方法有效,这限制了Aspect#的一部分应用。

Spring.Net从根本意义上来说,是对Spring工具从Java平台向.Net平台的完全移植。它在AOP的实现上与Spring几乎完全相似,仍然利用了AOP联盟提供的拦截器、Advice等实现AOP。Spring.Net的配置文件也与Spring相同。

Eos采用的是静态织入的技术。它提供了独有的编译器,同时还扩展了C#语法,以类似于AspectJ的结构,规定了一套完整的AOP语法,诸如aspect,advice,before,after,pointcut等。Eos充分的利用了.Net中元数据的特点,以IL级的代码对方面进行织入,这也使得它的性能与其他AOP工具比较有较大的提高。

4.2 .Net平台下实现AOP的技术基础

如前所述,在.Net平台下实现AOP,采用的方式主要是静态织入和动态织入的方式。在本文中,我将充分利用.Net的技术特性,包括元数据、Attribute、.Net Remoting的代理技术,将其综合运用,最终以动态织入的方式实现AOP公共类库。本节将介绍实现AOP所必需的.Net知识。

4.2.1元数据(metadata)
4.2.1.1元数据概述
元数据是一种二进制信息,用以对存储在公共语言运行库(CLR)中可移植可执行文件 (PE) 或存储在内存中的程序进行描述。在.Net中,如果将代码编译为 PE 文件时,便会将元数据插入到该文件的一部分中,而该代码被编译成的Microsoft 中间语言 (MSIL),则被插入到该文件的另一部分中。在模块或程序集中定义和引用的每个类型和成员都将在元数据中进行说明。执行代码时,运行库将元数据加载到内存中,并引用它来发现有关代码的类、成员、继承等信息。

元数据以非特定语言的方式描述在代码中定义的每一类型和成员。它存储的信息包括程序集的信息,如程序集的版本、名称、区域性和公钥,以及该程序集所依赖的其他程序集;此外,它还包括类型的说明,包括类型的基类和实现的接口,类型成员(方法、字段、属性、事件、嵌套的类型)。

在.Net Framework中,元数据是关键,该模型不再需要接口定义语言 (IDL) 文件、头文件或任何外部组件引用方法。元数据允许 .NET 语言自动以非特定语言的方式对其自身进行描述,此外,通过使用Attribute,可以对元数据进行扩展。元数据具有以下主要优点:

1. 自描述文件

公共语言运行库(CLR)模块和程序集是自描述的。模块的元数据包含与另一个模块进行交互所需的全部信息。元数据自动提供COM中IDL的功能,允许将一个文件同时用于定义和实现。运行库模块和程序集甚至不需要向操作系统注册。运行库使用的说明始终反映编译文件中的实际代码,从而提高应用程序的可靠性。

2.语言互用性和更简单的基于组件的设计

元数据提供所有必需的有关已编译代码的信息,以供您从用不同语言编写的 PE 文件中继承类。您可以创建用任何托管语言(任何面向公共语言运行库的语言)编写的任何类的实例,而不用担心显式封送处理或使用自定义的互用代码。

3.Attribute

.NET Framework允许在编译文件中声明特定种类的元数据(称为Attribute)。在整个 .NET Framework 中到处都可以发现Attribute的存在,Attribute用于更精确地控制运行时程序如何工作。另外,用户可以通过自定义属性向 .NET Framework 文件发出用户自己的自定义元数据。

4.2.1.2元数据的结构
在PE文件中与元数据有关的主要包括两部分。一部分是元数据,它包含一系列的表和堆数据结构。每个元数据表都保留有关程序元素的信息。例如,一个元数据表说明代码中的类,另一个元数据表说明字段等。如果您的代码中有10个类,类表将有10行,每行为1个类。元数据表引用其他的表和堆。例如,类的元数据表引用方法表。元数据以四种堆结构存储信息:字符串、Blob、用户字符串和 GUID。所有用于对类型和成员进行命名的字符串都存储在字符串堆中。例如,方法表不直接存储特定方法的名称,而是指向存储在字符串堆中的方法的名称。

另一部分是MSIL指令,许多MSIL指令都带有元数据标记。元数据标记在 PE 文件的 MSIL 部分中唯一确定每个元数据表的每一行。元数据标记在概念上和指针相似,永久驻留在MSIL中,引用特定的元数据表。元数据标记是一个四个字节的数字。最高位字节表示特定标记(方法、类型等)引用的元数据表。剩下的三个字节指定与所说明的编程元素对应的元数据表中的行。如果用C#定义一个方法并将其编译到PE文件中,下面的元数据标记可能存在于PE文件的MSIL部分:

本文来自[Svn中文网]转发请保留本站地址:http://www.svn8.com/uml/ms/uml200907167368.html

.Net平台与Java平台相比,由于它至今在服务端仍不具备与unix系统的兼容性,也不具备类似于Java平台下J2EE这样的企业级容器,使得.Net平台在大型的企业级应用上,常常为人所诟病。就目前而言,.Net平台并没有提供AOP技术的直接实现,而微软在未来对于.Net的发展战略目标,我们仍未可知。但我相信微软对于目前炙手可热的AOP技术应该不会视而不见。也许在未来的.Net平台下,会出现类似于Spring那样的轻量级IoC容器,加上O/R Mapping的进一步实现与完善,随着Windows Server操作系统的逐步推新,.Net平台对于企业级系统开发的支持会越来越多。 AOP技术在.Net平台中的应用,相较于Java平台而言,还远不够成熟,功能也相对较弱,目前能够投入商用的AOP工具几乎没有。借鉴Java开源社区的成功,.Net平台下AOP工具的开发也都依托于开源社区的力量。众多开源爱好者,仍然在坚持不懈对AOP技术进行研究和实践,试图找到AOP技术与.Net之间的完美结合点,从而开发出真正能够商用的功能强大的AOP工具。就目前而言,大部分在.Net平台下的AOP工具,大部分均脱胎于Java平台下的AOP工具,例如Spring.Net之于Spring,Eos之于AspectJ。由于Java平台和.Net平台在语言机制上的相似性,使得它们在实现AOP的技术机制上,大体相似,无非是利用静态织入或动态织入的方式,完成对aspect的实现。
目前在.Net平台下的AOP大部分仍然处于最初的开发阶段,各自发布的版本基本都是beta版。其中较有代表性的AOP工具包括Aspect#,Spring.Net,Eos等。
Aspect#是基于Castle动态代理技术实现的。Castle动态代理技术利用了.Net的Emit技术,生成一个新的类去实现特定的接口,或者扩展一个已有的类,并将其委托指向IInterceptor接口的实现类。通过Castle动态代理技术,就可以拦截方法的调用,并将Aspect的业务逻辑织入到方法中。利用Castle动态代理技术,最大的缺陷是它只对虚方法有效,这限制了Aspect#的一部分应用。
Spring.Net从根本意义上来说,是对Spring工具从Java平台向.Net平台的完全移植。它在AOP的实现上与Spring几乎完全相似,仍然利用了AOP联盟提供的拦截器、Advice等实现AOP。Spring.Net的配置文件也与Spring相同。
Eos采用的是静态织入的技术。它提供了独有的编译器,同时还扩展了C#语法,以类似于AspectJ的结构,规定了一套完整的AOP语法,诸如aspect,advice,before,after,pointcut等。Eos充分的利用了.Net中元数据的特点,以IL级的代码对方面进行织入,这也使得它的性能与其他AOP工具比较有较大的提高。
4.2 .Net平台下实现AOP的技术基础
如前所述,在.Net平台下实现AOP,采用的方式主要是静态织入和动态织入的方式。在本文中,我将充分利用.Net的技术特性,包括元数据、Attribute、.Net Remoting的代理技术,将其综合运用,最终以动态织入的方式实现AOP公共类库。本节将介绍实现AOP所必需的.Net知识。
4.2.1元数据(metadata)4.2.1.1元数据概述元数据是一种二进制信息,用以对存储在公共语言运行库(CLR)中可移植可执行文件 (PE) 或存储在内存中的程序进行描述。在.Net中,如果将代码编译为 PE 文件时,便会将元数据插入到该文件的一部分中,而该代码被编译成的Microsoft 中间语言 (MSIL),则被插入到该文件的另一部分中。在模块或程序集中定义和引用的每个类型和成员都将在元数据中进行说明。执行代码时,运行库将元数据加载到内存中,并引用它来发现有关代码的类、成员、继承等信息。
元数据以非特定语言的方式描述在代码中定义的每一类型和成员。它存储的信息包括程序集的信息,如程序集的版本、名称、区域性和公钥,以及该程序集所依赖的其他程序集;此外,它还包括类型的说明,包括类型的基类和实现的接口,类型成员(方法、字段、属性、事件、嵌套的类型)。
在.Net Framework中,元数据是关键,该模型不再需要接口定义语言 (IDL) 文件、头文件或任何外部组件引用方法。元数据允许 .NET 语言自动以非特定语言的方式对其自身进行描述,此外,通过使用Attribute,可以对元数据进行扩展。元数据具有以下主要优点:
1. 自描述文件
公共语言运行库(CLR)模块和程序集是自描述的。模块的元数据包含与另一个模块进行交互所需的全部信息。元数据自动提供COM中IDL的功能,允许将一个文件同时用于定义和实现。运行库模块和程序集甚至不需要向操作系统注册。运行库使用的说明始终反映编译文件中的实际代码,从而提高应用程序的可靠性。
2.语言互用性和更简单的基于组件的设计
元数据提供所有必需的有关已编译代码的信息,以供您从用不同语言编写的 PE 文件中继承类。您可以创建用任何托管语言(任何面向公共语言运行库的语言)编写的任何类的实例,而不用担心显式封送处理或使用自定义的互用代码。
3.Attribute
.NET Framework允许在编译文件中声明特定种类的元数据(称为Attribute)。在整个 .NET Framework 中到处都可以发现Attribute的存在,Attribute用于更精确地控制运行时程序如何工作。另外,用户可以通过自定义属性向 .NET Framework 文件发出用户自己的自定义元数据。
4.2.1.2元数据的结构在PE文件中与元数据有关的主要包括两部分。一部分是元数据,它包含一系列的表和堆数据结构。每个元数据表都保留有关程序元素的信息。例如,一个元数据表说明代码中的类,另一个元数据表说明字段等。如果您的代码中有10个类,类表将有10行,每行为1个类。元数据表引用其他的表和堆。例如,类的元数据表引用方法表。元数据以四种堆结构存储信息:字符串、Blob、用户字符串和 GUID。所有用于对类型和成员进行命名的字符串都存储在字符串堆中。例如,方法表不直接存储特定方法的名称,而是指向存储在字符串堆中的方法的名称。
另一部分是MSIL指令,许多MSIL指令都带有元数据标记。元数据标记在 PE 文件的 MSIL 部分中唯一确定每个元数据表的每一行。元数据标记在概念上和指针相似,永久驻留在MSIL中,引用特定的元数据表。元数据标记是一个四个字节的数字。最高位字节表示特定标记(方法、类型等)引用的元数据表。剩下的三个字节指定与所说明的编程元素对应的元数据表中的行。如果用C#定义一个方法并将其编译到PE文件中,下面的元数据标记可能存在于PE文件的MSIL部分:

0x06000004

最高位字节 (0x06) 表示这是一个MethodDef标记。低位的三个字节 (000004) 指示公共语言运行库在 MethodDef 表的第四行查找对该方法定义进行描述的信息。

表4.1 描述了PE文件中元数据的结构及其每部分的内容:

PE部分

PE部分的内容

PE标头

PE文件主要部分的索引和入口点的地址。

运行库使用该信息确定该文件为 PE 文件并确定当将程序加载到内存时执行从何处开始。

MSIL指令

组成代码的 Microsoft 中间语言指令 (MSIL)。许多 MSIL 指令带有元数据标记。

元数据

元数据表和堆。运行库使用该部分记录您的代码中每个类型和成员的信息。本部分还包括自定义属性和安全性信息。

表4.1 PE文件中的元数据

4.2.1.3元数据在运行时的作用

由于在MSIL指令中包含了元数据标记,因此,当公共语言运行库(CLR)将代码加载到内存时,将向元数据咨询该代码模块中包含的信息。运行库对Microsoft 中间语言 (MSIL) 流执行广泛的分析,将其转换为快速本机指令。运行库根据需要使用实时 (JIT) 编译器将 MSIL 指令转换为本机代码,每次转换一个方法。例如,有一个类APP,其中包含了Main()方法和Add()方法:

using System;

public class App

{

public static int Main()

{

int ValueOne = 10;

int ValueTwo = 20;

Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo));

return 0;

}

public static int Add(int One, int Two)

{

return (One + Two);

}

}

通过运行库,这段代码被加载到内存中,并被转化为MSIL:

.entrypoint

.maxstack 3

.locals ([0] int32 ValueOne,

[1] int32 ValueTwo,

[2] int32 V_2,

[3] int32 V_3)

IL_0000: ldc.i4.s 10

IL_0002: stloc.0

IL_0003: ldc.i4.s 20

IL_0005: stloc.1

IL_0006: ldstr "The Value is: {0}"

IL_000b: ldloc.0

IL_000c: ldloc.1

IL_000d: call int32 ConsoleApplication.MyApp::Add(int32,int32) /* 06000003 */

JIT 编译器读取整个方法的 MSIL,对其进行彻底地分析,然后为该方法生成有效的本机指令。在 IL_000d 遇到 Add 方法 (/* 06000003 */) 的元数据标记,运行库使用该标记参考 MethodDef 表的第三行。

表4.2显示了说明 Add 方法的元数据标记所引用的 MethodDef 表的一部分:



相对虚拟地址 (RVA)

ImplFlags

Flags

Name

(指向字符串堆)

Signature

(指向 Blob 堆)

1

0x00002050

IL

Managed

Public

ReuseSlot

SpecialName

RTSpecialName

.ctor

.ctor(构造函数)

2

0x00002058

IL

Managed

Public

Static

ReuseSlot

Main

String

3

0x0000208c

IL

Managed

Public

Static

ReuseSlot

Add

int, int, int

表4.2 元数据标记

该表的每一列都包含有关代码的重要信息。RVA 列允许运行库计算定义该方法的 MSIL 的起始内存地址。ImplFlags 和 Flags 列包含说明该方法的位屏蔽(例如,该方法是公共的还是私有的)。Name 列对来自字符串堆的方法的名称进行了索引。Signature 列对在 Blob 堆中的方法签名的定义进行了索引。

通过利用元数据,我们就可以获得类的相关信息。如上所述,在类APP的MethodDef表中,可以获得类APP的三个方法,以及方法的Flags和方法签名。而在.Net中,则提供了反射技术,来支持这种对元数据信息的获取。可以说,正是因为有了元数据,才使得AOP的拦截与织入功能的实现成为可能。

4.2.2 Attribute

4.2.2.1 Attribute概述

通过对.Net元数据的分析,我们知道可以通过Attribute来扩展元数据。那么什么是Attribute?在MSDN中,Attribute被定义为“是被指定给某一声明的一则附加的声明性信息”。 我们可以通过Attribute来定义设计层面的信息以及运行时(run-time)信息,也可以利用Attribute建立自描述(self-describing)组件。

Attribute可应用于任何目标元素,我们可以通过AttributeTargets枚举指定其施加的目标,AttributeTargets枚举在.Net中的定义如下:

public enum AttributeTargets

{

All=16383,

Assembly=1,

Module=2,

Class=4,

Struct=8,

Enum=16,

Constructor=32,

Method=64,

Property=128,

Field=256,

Event=512,

Interface=1024,

Parameter=2048,

Delegate=4096,

ReturnValue=8192

}

作为参数的AttributeTarges的值允许通过“或”操作来进行多个值的组合,如果你没有指定参数,那么默认参数就是All 。

不管是.Net Framework提供的Attribute,还是用户自定义Attribute,都是通过[]施加到目标元素上。虽然Attribute的用法与通常的类型不一样,但在.Net内部,Attribute本质上还是一个类。但是,Attribute类的实例化发生在编译时,而非运行时,因而达到了扩展元数据的目的。一个Attribute的多个实例可应用于同一个目标元素;并且Attribute可由从目标元素派生的元素继承。

4.2.2.2自定义Attribute

.Net Framework支持用户自定义Attribute。自定义Attribute的方法与定义类一样,唯一不同之处是自定义的Attribute必须继承Attribute类。Attribute类包含用于访问和测试自定义Attribute的简便方法。其中,Attribute类的构造函数为protected,只能被Attribute的派生类调用。Attribute类包含的方法主要为:

1.三个静态方法

static Attribute GetCustomAttribute():这个方法有8种重载的版本,它被用来取出施加在类成员上指定类型的Attribute。

static Attribute[] GetCustomAttributes(): 这个方法有16种重载版本,用来取出施加在类成员上指定类型的Attribute数组。

static bool IsDefined():有八种重载版本,看是否指定类型的定制attribute被施加到类的成员上面。

2.两个实例方法

bool IsDefaultAttribute(): 如果Attribute的值是默认的值,那么返回true。

bool Match():表明这个Attribute实例是否等于一个指定的对象。

3.公共属性

TypeId: 得到一个唯一的标识,这个标识被用来区分同一个Attribute的不同实例。

通过自定义Attribute,可使得用户自定义的信息与Attribute施加的类本身相关联。例如,给定一个自定义的 .NET 属性,我们就可以轻松地将调用跟踪Attribute与类的方法相关联:

public class Bar

{

[CallTracingAttribute("In Bar ctor")]

public Bar() {}

[CallTracingAttribute("In Bar.Calculate method")]

public int Calculate(int x, int y){ return x + y; }

}

请注意,方括号中包含 CallTracingAttribute 和访问方法时输出的字符串。这是将自定义元数据与 Bar 的两个方法相关联的Attribute语法。该自定义的Attribute实现,如下所示:

using System;

using System.Reflection;

[AttributeUsage( AttributeTargets.ClassMembers, AllowMultiple = false )]

public class CallTracingAttribute : Attribute

{

private string m_TracingInfo;

public CallTracingAttribute(string info)

{

m_TracingInfo = info;

}

public string TracingInfo

{

get {return tracingInfo;}

}

}

通过自定义的CallTracingAttribute,将一段Tracing信息施加到类Bar的构造函数和方法Calculate上。我们可以利用反射技术与Attribute类提供的方法,来获得Bar类的元数据中包含的Attribute信息,如:

public class Test

{

public static void Main(string[] args)

{

System.Reflection.MemberInfo info = typeof(Bar);

CallTracingAttribute attribute = (CallTracingAttribute) Attribute.GetCustomAttribute(info,typeof(CallTracingAttribute));

if (attribute != null)

{

Console.WriteLine(“Tracing Information:{0}”,attribute.TracingInfo);

}

}

}

4.2.2.3上下文(Context)和Attribute

所谓上下文(Context),是指一个逻辑上的执行环境。每一个应用程序域都有一个或多个Context,.Net中的所有对象都会在相应的Context中创建和运行。如图4.1所示,它显示了一个安全地存在于Context的对象:



本文来自:http://www.svn8.com/uml/ms/uml200907167368.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: