`
seara
  • 浏览: 646490 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

颠覆C#王权的“魔比斯环” — 实现AOP框架的终极利器

阅读更多

本文为原创,如需转载,请注明作者和出处,谢谢!

时间要追溯到2005年。那时正在做硕士论文。题目是“AOP framework for .net”。这个AOP框架将使用C#2.0来实现。 这当然没什么令人惊奇的。从理论上说,任何开发语言都可以实现AOP框架。但要按着AOP联盟的规范实现这个AOP框架,大多数的开发语言并不能很容易地完成这项任务。

微软公司在我们心目中是强大的,而出自于微软的C#自然也会被认为是强大的。使用C#几乎可以很容易地完成大多数的应用程序(包括桌面、Web、移动等)。但要用C#来实现标准的AOP框架却不是那么容易,甚至有点强人所难。这到底是为什么呢?

一、 AOP的概念和原理

AOP(Aspect Oriented Programming)的中文意思是“面向方面编程”。它是10年前由施乐公司提出的。这种技术并不是一种新的编程技术,和OOP(Object Oriented Programming)并不是平级的。它只是OOP的一种扩展。

一个典型的软件系统由核心功能和系统功能组成。所 谓核心功能就是和这个系统相关的业务功能,如在mail服务器中的核心功能是接收和发送电子邮件。系统功能则可以看成是系统的可选功能,如日志、安全等。 如果没有了这些功能,mail服务器仍可以照常工作,只是不太安全了,并且无法查找以往的记录。实现这些系统功能,一般的作法是将系统功能的代码和核心功 能的代码混在一起。这样做不仅加大了系统设计和实现的难度,而且使设计系统核心功能的程序员必须要考虑这些系统的功能,分散了他们的注意力。

由 于在软件开发中遇到以上问题,人们开始设想是否能将软件中的核心功能和系统功能分开,达到单独设计、实现、维护的目的。于是就有人提出在设计和实现时单独 进行,当编译时将单独编写的系统功能的代码织入(weaves)到核心功能的代码中,将代码织入的工具叫织入器(weaver)。这种思想经过长期的实 践,就逐渐形成了AOP的核心思想,同时也形成了AOP最早的一个实现:AspectJ。

AOP构建在已有的技术基础之上,同时提供了自己的一套额外机制,也就是Aspect机制,对系统进行描述、设计和实现。AOP要保证这些机制在概念上简洁,执行效率高。其基本思想是通过分别描述系统的不同关注点(属性或者兴趣)及其关系,以一种松耦合的方式实现单个关注点,然后依靠AOP环境的支撑机制,将这些关注点组织或编排成最终的可运行程序。关注点包括普通关注点和系统的贯穿特性。通常可以使用传统的结构化方法和面向对象方法提供的机制,对普通关注点进行处理;使用Aspect机制,对贯穿特性进行处理。系统的贯穿特性范围包括了从高层的关注目标,比如安全和服务质量,到低层的关注目标比如缓存处理等。贯穿特性可以是功能性的,比如事务规则,也可以是非功能的,比如同步和交易管理等。AOP将传统方法学中分散处理的贯穿特性实现为系统的一类元素—Aspect,并将它们从类结构中独立了出来,成为单独的模块。

为了支持上述的系统实现和运行过程,AOP系统首先必须提供一种声明Aspect的机制,包括Aspect如何声明,连接点(Aspect代码与其它代码的交互点)如何定义,Aspect代码如何定义,Aspect的参数化程度和可定制性等。其次,需要提供一种将Aspect代码和基础代码组合编排(Weaving)在一起的机制,包括定义编排语言和规则,解决Aspect之间潜在的冲突,为组装和执行建立外部约束等。最后,必须有生成可运行系统的实现机制,包括系统提供什么组合机制,是编译时刻静态组装,还是运行时动态组装;对程序单元分别进行编译的模块化编译机制;对AOP机制和现有系统兼容性的规约;对组装结果的验证机制等。

二、C#限制颇多,实现AOP框架困难重重

虽然诞生于10多年前的AOP技术在近几年开始逐渐流行起来。有一些应用很广的软件或框架,如JBossSpring,都使用了AOP技术。但这些AOP技术大多是基于java的。如AspectJJBoss AOPSpring AOP。但是AOP技术在.net环境下应用得很少,其于.netAOP 框架也不多。形成这种情况的原因很多。众所周知,实现AOP一般有两种方法。一种是利用动态代理或其它技术在程序运行时对方法等信息进行监控(如JBoss AOPSpring AOP),即动态AOP。另外一种是直接在编译器中支持,就象AspectJ。但这种做法实现的难度较大,大多数AOP框架的实现都是采用了第一种方法。而动态代理技术在.net(不管是VB.netC#都一样)实现是非常困难的,在.net中并不象java提供了动态代理实现机制。在.net中要想实现动态代理必须得直接使用IL(Intermediate Language)写代码,这样就必须对IL有非常深入的了解。

由于要在C#中实现这个AOP框架,因此,我不可能再自己做个编译C#编译器甚至虚拟机。所以只能使用动态代理的方式来实现。但使用动态代理需要解决两个问题。

1. 如何动态生成代理类。

2. 如何拦截构造方法。

1. 如何动态生成代理类

在阐述问题之前,先让我们看一看什么叫代理类。代理类也是普通的类。只是这个类要继承于被代理的类。而且在代理类中的方法要覆盖父类中的方法。如类Class1的代码如下:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->classClass1
{
publicvirtualvoidMethod1(){}
publicvirtualStringMethod2(){returns;}

publicClass1(Strings){}
}

上面的Class1有一系列的virtual方法,并且有一个构造方法。下面让我们来编写一个代理Class1的代理类ProxyClass

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->classProxyClass:Class1
{
publicoverridevoidMethod1(){base.Method1();}
publicoverrideStringMethod2(){Strings=base.Method2();returns}

publicClass2(Strings):base(s){}
}

ProxyClass类可以看出,在使用下面代码创建Class1对象后,仍然会得到Class1类中相应方法执行后的结果:

Class1 class1 = new ProxyClass(“hello world”);

虽然上面的代码可能大多数程序员都能理解(就是多态调用),但实际上这种代码对基于动态代理技术的AOP框架是毫无用处的。之所以叫动态代理,就是在程序执行时自动生成代理类。而上面的代码是静态地写到程序中的,在编译后无法更改。也许有人会说,根据Class1可以自动生成ProxyClass类的源码,然后在内存或硬盘上编译再调用不就可以了!这种做法可以是可以,但效率却非常的低。大家可以想象,如果每执行一个方法,就生成一堆C#源代码,再编译,恐怕C#就比脚本语言还慢了。

看到这些,也许那些C#.net高手会说,直接生成MSIL不就行了。但你要知道,虽然MSIL没有汇编复杂,可对于大多数程序员来说,是可望而不可及的。因此,这个技术问题就成为实现AOP框架的第一个大障碍。

2. 如何拦截构造方法

AOP最重要的特性就是对方法的拦截,这其中也包括构造方法。所谓拦截,就是在执行原方法时,要在两个位置来执行我们织入的代码,如日志代码。这两个位置是before(在原代码执行之前)和after(在原代码执行之后)。当然,还可以对源代码进行around(完全取代原来的代码)。这对于普通的方法来说并不算什么,如上面的Method1方法。要想在ProxyClass类中的Method1方法拦截Class1类中的方法,beforeafteraround的实现分别如下:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->before:
publicoverridevoidMethod1(){base.Method1();}

after:
publicoverridevoidMethod1(){base.Method1();}

around:
publicoverridevoidMethod1(){}

但这对于构造方法来说却无法实现。因此,在子类中调用父类中的构造方法只能在方法后面调用,如下面代码所示:

public Class2(String s) : base(s) {...}

从上面的代码可以看出,构造方法只能实现before,而不能实现afteraround。也就是在子类中无论写不写base关键字,必须调用父类中的构造方法(如果不写,调用父类中无参数的默认构造方法)。更不会有下面的代码形式:

public Class2(String s) {... base(s);}

因此,如何使构造方法也拥有afteraround特性,就成为实现AOP构架的第二个拦路虎。

三、偶遇通往MSIL世界的“魔比斯环”,C#王权土崩瓦解

可能每个人都向往着穿越时空。然而有一群幸运的科学家却做到了。这些科学家在一个深谷中偶然发现了一个可以通往另一个世界的大门,这就是“魔比斯环”。通过“魔比斯环”,不仅能穿越时空,同时也将拥有无穷无尽的力量,尽管这么做会有带来一定的危险。

上面所描述的只是科幻电影中的场景。然而在现实世界也确实存在着很多的“魔比斯环”。如我们使用的C#就是这样。虽然C#是强大的,但我们必须受到C#语法和约定的限制。如一个重要限制是任何子类在创建其对象实例时,在子类的构造方法中必须调用父类的一个构造方法,如果不调用,C#编译器会自动在子类的构造方法加入调用父类构造方法的语句(无参数的默认构造方法)。我们可以先看看下面两个类:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->classParentClass
{
publicParentClass()
{
System.Console.WriteLine(
"ParentClass");
}
}

classChildClass:ParentClass
{
publicChildClass()
{
System.Console.WriteLine(
"ChildClass");
}
}

如果在控制台程序中创建ChildClass对象,将输出如下的结果:

ParentClass

ChildClass

让我们用微软提供的反编译工具ildasm.exe(位置:C:"Program Files"Microsoft Visual Studio 8"SDK"v2.0"Bin"ildasm.exe)来反编译exe,看看为什么会这样。用ildasm.exe打开exe后的界面如图1所示:


1

双击图1所选中的部分,将会显示如下的中间语言代码:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->.methodpublichidebysigspecialnamertspecialname
instancevoid.ctor()cilmanaged
{
.maxstack8
IL_0000:ldarg.0
IL_0001:callinstancevoidConsoleApplication1.ParentClass::.ctor()
IL_0006:nop
IL_0007:nop
IL_0008:ldstr"ChildClass"
IL_000d:callvoid[mscorlib]System.Console::WriteLine(string)
IL_0012:nop
IL_0013:nop
IL_0014:ret
}

读者可以不必管这些代码 是什么意思,只要注意上面黑体字部分。C#编译器已经自动加入了调用ParentClass的构造方法的代码(.ctor()表示构造方法),这一行是去 不掉的。找到问题所在,就意味着离成功又近了一步。从中间代码可以看出,如果不让C#编译器加上这行不就行了吗?但我又不能再设计一个C#编译器,因此, 只能利用C#原来的编译器。

根据上面所述,现在我只要解决一个问题即可,就是要利用C#编译器来改变C#的规则。这就要求不能使用C#的代码,而构造方法的代码必须直接使用中间语言来完成(并不是象编译器一样直接生成dll或exe)。

这可真给我出了个难题。不过C#和.net framework的强大使用坚信一定有方法解决,只是我暂时没找到而已。

由于大多数使用虚拟机的 语言都支持反射功能,因此,我决定碰碰运气,看看是否能通过反射解决这个问题。在.net中有一个命名空间System.Reflection。看了一下 其中的类,基本都是用来得到信息的(方法、属性、Assembly的信息),并没有写入信息的(就是写入IL)。当我快要绝望的时候,终于眼前一亮。在 System.Reflection中又发现了一个Emit命名空间。Emit的中文含义是“发出”的意思,想到此,我的希望又重新燃起。于是上MSDN 查了关于Emit的资料。MSDN上说Emit的主要功能就是通过C#语言绕过C#编译器直接向内存中写入IL,相当于从硬盘上读取IL一样。yeah, 这正是我需要的。有了这个,就可以动态生成代理类了。而且速度和静态类是一样的。

在Emit中提高了IL指令的全部映射。如下面的代码将直接用IL生成一个方法及其实现:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->publicdelegateStringMyMethod(Stringstr);

privatevoidGenerateMethod()
{
Type[]argsType
={typeof(String)};
DynamicMethoddm
=newDynamicMethod("MyMethod1",
typeof(String),
argsType,
typeof(Form1).Module);

ILGeneratoril
=dm.GetILGenerator();

il.Emit(OpCodes.Ldstr,
"hello:");
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call,
typeof(String).GetMethod("Concat",newType[]{typeof(String),typeof(String)}));
il.Emit(OpCodes.Ret);
MyMethodmyMethod
=(MyMethod)dm.CreateDelegate(typeof(MyMethod));
MessageBox.Show(myMethod(
"myfriends"));
}

上面的GenerateMethod方法动态地生成了一个MyMethod1方法,并显示如图2的对话框:

2

在上面代码中主要有三个地方需要提一下:

1. OpCodes包含了IL的所有的指令。如OpCodes.Ldstr表示将一个字符串压栈(每个方法都有一个操作栈),OpCodes.Call表示调用C#中的其他方法。

2. DynamicMethod类可以建立动态的普通方法。如果想建立构造方法,可以使用ConstructorInfo。

3. 所有的IL代码都是通过ILGenerator的Emit方法写入内存的。

Emit就象电影中的“魔比斯环”一样,可以很容易地突破C#的限制,从而穿越了C#世界,到达了另外一个MSIL世界。这将为我们实现另人振奋的功能提供了可能性。

四、在MSIL世界建立起强大的AOP帝国

既然能用C#直接写MSIL,那么就可容易编写AOP框架了。虽然这是用C#代码写MSIL,但也要对MSIL有一定的了解,感兴趣的读者可以到微软网站去下载IL Specification

由于这个AOP框架的代码十分庞大,在这里只给出了一些代码片段。实现AOP框架的核心就是生成动态代理类。因此,使用IL生成代理类的框架是第一步。下面是生成代理类框架的核心代码:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->publicTypeBuilderGenerateType()//返回动态代理类的Type
{
stringclassName=GetNewClassName();
TypeAttributestypeAttributes
=TypeAttributes.Class|TypeAttributes.Public|TypeAttributes.Sealed;
TypeBuildertypeBuilder
=m_EmitClassInfo.Module.DefineType(className,
typeAttributes,m_EmitClassInfo.BaseType);
returntypeBuilder;
}

从上面的代码很容易猜到我要生成一个publicsealed类(不可继承)。接下来就是根据父类生成相应的方法(包括普通方法和构造方法),下面是一些代码片段:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->privatevoidGenerateMethod()//生成普通的方法
{
MethodAttributesmethodAttributes
=MethodAttributes.Public;
MethodBuildermethodBuilder
=
m_TypeBuilder.DefineMethod(
"__GetMethodInvocation",methodAttributes,typeof(IMethodInvocation),newType[]{typeof(ICallable),typeof(MethodInfo)});
m_EmitClassInfo.__GetMethodInvocation
=methodBuilder;

ILGeneratorilGenerator
=methodBuilder.GetILGenerator();
LabelexecIfLabel
=ilGenerator.DefineLabel();
LabelendIfLabel
=ilGenerator.DefineLabel();
LocalBuildermethodInvocation
=ilGenerator.DeclareLocal(typeof(IMethodInvocation));
ilGenerator.Emit(OpCodes.Ldarg_0);

}

privatevoidGenerateConstructor()//生成构造方法
{
try
{
MethodAttributesmethodAttributes
=MethodAttributes.Public;
CallingConventionscallingConventions
=CallingConventions.Standard;
m_BaseConstructorParams
=m_EmitClassInfo.ConstructorArgumentsType;
m_ConstructorParams
=newType[m_BaseConstructorParams.Length+1];
m_BaseConstructorParams.CopyTo(m_ConstructorParams,
0);
m_ConstructorParams[m_BaseConstructorParams.Length]
=typeof(IInterceptor);
m_constructorBuilder
=m_TypeBuilder.DefineConstructor(methodAttributes,callingConventions,m_ConstructorParams);
m_EmitClassInfo.Constructor
=m_constructorBuilder;
m_IlGenerator
=m_constructorBuilder.GetILGenerator();
}
catch(Exceptione)
{
throwe;
}
}

使用Emit不仅仅可以实现AOP框架,还可以根据用户需要自动生成任何IL代码。这些IL代码不会受到VB.netC#的限制。因此,用户可以通过Emit来优化生成的中间语言代码,并完成一些C#无法做到的任务。如果读者想了解Emit的详细用法,可以参考MSDN或其他的相关文档。

分享到:
评论

相关推荐

    C# 实现 AOP微型框架

    C#实现AOP微型框架是将面向切面编程(Aspect-Oriented Programming)的理念应用于C#编程中的一个实践。在VS2008环境下开发的这个框架,为开发者提供了一种方便的方式来插入横切关注点,如日志、事务管理、性能监控等...

    C# 实现的AOP框架

    C#实现的AOP框架可以通过动态代理、编译时织入或运行时织入等方式实现。 C#中常见的AOP框架有Unity、Autofac、Castle Windsor等,它们提供了方便的方式来定义和注入切面。例如,Castle Windsor框架利用其动态代理库...

    利用C#实现AOP常见的几种方法详解

    在C#中,实现AOP的方法多种多样,以下将详细介绍几种常见的实现方式。 1. **静态织入**: 静态织入是在编译时完成的,它通过编译器或者编译插件(如PostSharp)在目标类的代码中插入拦截逻辑。这种方式的优点是...

    C#实现的IOC和AOP框架,供学习

    这个名为“GreeFramOfficial”的压缩包文件,很可能是提供了一个基于C#实现的IOC和AOP框架,供开发者学习和使用。 IOC(Inversion of Control)的核心思想是将对象的创建和管理交给一个容器来处理,而不是由对象...

    C# 静态注入实现AOP

    Dpeng.AOP可能是一个实现AOP的C#库,它基于`Mono.Cecil`,提供了更高层次的抽象,简化了上述步骤,使开发者更容易在C#项目中实现AOP。这个库可能包含以下功能: - 自动识别需要拦截的方法,如标记特定属性的方法。 ...

    C#自己实现AOP的事务操作

    C#作为.NET框架的主要编程语言,提供了强大的支持来实现AOP,特别是在处理事务管理方面。事务是数据库操作的核心概念,确保数据一致性并能够在异常情况下进行回滚。 C#实现AOP事务的关键在于利用特性(Attribute)...

    Spring AOP框架实现的结构分析

    "Spring AOP 框架实现的结构分析" 本文主要目标是从实现的角度来认识 SpringAOP 框架。首先,我们需要了解 AOP 的基本概念,包括关注点、核心关注点、横切关注点、方面、连接点、切入点、增强、引介、混入继承和织...

    基于C#语言的BSF.Aop静态AOP织入设计源码框架

    该项目是一款基于C#语言的BSF.Aop静态AOP织入设计源码框架,包含49个文件,包括28个C#源文件、7个DLL库文件、2个项目文件、2个文本文件、1个Git忽略文件、1个用户文件、1个资源文件、1个设置文件、1个可执行文件和1...

    仿 Spring 手写 IoC、AOP 框架.rar

    实现AOP框架,你需要考虑以下关键点: 1. **定义切面**:创建一个类或接口来表示切面,其中包含切点表达式(Pointcut Expression)和通知。 2. **匹配切点**:解析切点表达式,确定哪些方法会触发通知的执行。 3. **...

    C# .net Aop 动态截获异常

    在提供的"ExinScada.Aop"压缩包中,可能包含了具体的AOP实现示例,包括特性定义、代理类实现以及如何与依赖注入框架集成的代码。通过对这些文件的分析和学习,开发者可以进一步理解如何在实际项目中应用C#和AOP来...

    基于Bytebuddy的Java Agent AOP框架.zip

    ByteBuddy是Java的一个库,用于在运行时创建和修改Java类和接口,它是创建Java代理或者实现AOP(面向切面编程)框架的理想工具。在本项目"基于Bytebuddy的Java Agent AOP框架.zip"中,我们将探讨如何使用ByteBuddy...

    仿springAOP框架

    **仿Spring AOP框架详解** 在Java开发领域,Spring框架以其强大的功能和灵活性深受开发者喜爱,其中AOP(面向切面编程)是其核心特性之一。AOP允许开发者将关注点从主业务逻辑中分离出来,如日志记录、事务管理等,...

    C#版本AOP注入

    C#作为.NET框架的主要编程语言,也支持AOP的实现。本文将深入探讨C#中的AOP注入,以及如何通过它来扩展代码、简化后期维护、控制权限和处理异常。 首先,我们要理解AOP的核心概念:切面、连接点、通知和织入。切面...

    DELPHI AOP框架(meAOPen)

    Delphi AOP框架,如meAOPen,为Delphi开发环境提供了一种实现AOP的工具。它允许开发者定义所谓的“切面”(Aspects),这些切面包含了特定的关注点,可以跨多个类或函数进行应用。通过这种方式,meAOPen可以帮助...

    简易的AOP框架

    在Java中,Spring框架是最著名的AOP实现之一,但这里我们讨论的是一个简易的AOP框架,它可以帮助理解AOP的基本概念和工作原理。 该简易AOP框架包含以下几个关键组件: 1. **配置文件**:这是定义切面(aspect)和...

    C# AOP帮助类

    1. **PostSharp**:这是一个编译时AOP框架,通过IL后处理实现切面的织入。PostSharp允许开发者定义自定义的属性作为切面,并在编译时自动应用到标记的类或方法。 2. **Unity Container**:这是微软的依赖注入容器,...

    使用Spring的注解方式实现AOP的细节

    在Spring框架中,面向切面编程(AOP)是一种强大的工具,它允许程序员定义横切关注点,如日志、事务管理、权限控制等,这些关注点可以被模块化并独立于业务逻辑进行处理。本篇文章将深入探讨如何通过Spring的注解...

    C#AOP的源码

    C#作为.NET框架的主要编程语言,支持多种方式实现AOP,其中包括Autofac和Castle Windsor这两个流行的依赖注入容器,它们都提供了AOP功能。 Autofac是.NET平台的一个轻量级IoC(Inversion of Control)容器,同时也...

    c# aop+mvc+facAop

    在C#中,通常使用PostSharp库来实现AOP。 另一方面,MVC(模型-视图-控制器)是一种设计模式,广泛应用于Web应用开发,以分离业务逻辑、用户界面和数据管理。在ASP.NET MVC框架中,开发者可以创建可测试、模块化的...

Global site tag (gtag.js) - Google Analytics