AOP in .NET

最近公司分配了项任务,让我调查一下.NET世界中的AOP。需求有四

  1. 什么是AOP?有什么用途?
  2. AOP的编程模型
  3. .NET世界中的AOP框架介绍极其评比
  4. 最佳实践

 

本文将假设读者对AOP的相关原理概念有了一定的了解,直接从第三部分开始。

 

实现方式

From Ayende Rahien’s blog post

Approach

Advantages

Disadvantages

Remoting Proxies

Easy to implement, because of the .Net framework support

Somewhat heavy weight
Can only be used on interfaces or MarshalByRefObjects

Deriving from ContextBoundObject

Easiest to implement
Native support for call interception

Very costly in terms of performance

Compile-time subclassing
( Rhino Proxy )

Easiest to understand

Interfaces or virtual methods only

Runtime subclassing
( Castle Dynamic Proxy )

Easiest to understand
Very flexible

Complex implementation (but already exists)
Interfaces or virtual methods only

Hooking into the profiler API
( Type Mock )

Extremely powerful

Performance?
Complex implementation (COM API, require separate runner, etc)

Compile time IL-weaving 
( Post Sharp / Cecil )

Very powerful
Good performance

Very hard to implement

Runtime IL-weaving
( Post Sharp / Cecil )

Very powerful
Good performance

Very hard to implement

来看看具体的缺点

  • 使用remoting proxies / ContextBoundObject
    • 无法对this的调用进行intercept,因为CLR保证this不会是代理
    • 性能影响很大,比如使用继承ContextBoundObject的方式会造成运行时context切换
  • SubClassing
    • 需要类型不能为sealed且只能override virtual方法。
    • 不能改写constructor
  • Weaving
    • 如果是source code层级的织入,需要对每种.Net支持的语言提供分别的支持
    • 无论是source code层级还是IL层级的织入,都有可能在类型变更的时候遭到破坏/给类型造成破坏。

 

现有的一些.Net的AOP框架

Project

Status

License

Approach

Document & Support

More Description

AspectDng

Updated to 1.0.3

GPL 2+

Look here for detail

Perhaps Compile time IL injection?

This project is not supported any more.

Relies on Cecil.

Post Sharp

Updated to 2.0.8.1323

Free, Commercial

Compile time IL injection

Commercial support.

Well documented.

 

Castle dynamic proxy

Updated to 2.5.2 at November 15, 2010

 

Dynamic proxy

Out of date documents

Multipurpose framework

EOS

Updated to 0.3.4

Free, Commercial

Custom Complier

 

Only tested with .Net 1.x

Aspect.NET

Updated to Beta(Version 8) at July 29 2009

 

Perhaps Compile time IL injection?

 

Relies on Phoenix

Aspect Sharp (Aspect#)

Updated to 2.1.1.0 on May 16, 2005

Apache 2.0

?

   

Compose*

Updated to 0.9.5 at August 5 2008

BSD, LGPL

?

   

Rapier-LOOM.NET

Updated to 2.21 at November 26 2007

?

Runtime IL injection

   

Gripper-LOOM.NET

Updated to 0.92 at January 17 2008

?

Compile time IL injection

 

Relies on Rapier-LOOM.NET

PIAB

Updated to 5.0 at April 2010

Ms-PL

Runtime proxy

Well documented.

Multipurpose framework

LinFu

Updated to 2.3 on January 02, 2011

LGPL V3

Runtime proxy

Out of date documents.

Relies on Cecil. Multipurpose framework

heredar

Updated to Alpha 1 on January 31, 2011

?

Static mixins

 

Relies on Cecil.

Spring.Net

Updated to 1.3.1

Apache 2.0

Runtime proxy

Well documented.

Commercial support and training.

Multipurpose framework

 

来看几个框架的一些具体介绍

关于几个IL织入的框架

技术上来说,能够支持IL织入的框架是一定能实现AOP的,比如Mono Cecil, MS CCI, MS Phoenix, and Rail。但是由于他们不是专门做AOP的框架,在之后的评比中没有考虑他们。

PIAB

PIAB (The Policy Injection Application Block) 是MS Enterprise Library的一部分。已经内嵌的支持如下aspects

  • Authorization
  • Exception Handling
  • Logging
  • Validation
  • Performance Counter
  • Caching (Caching handler is removed from EntLib 5.0 due to some issues)

Spring.Net

已经内嵌的支持如下aspects。

  • Caching
  • Exception Handling
  • Logging
  • Retry
  • Transaction Management
  • Parameter Validation

Post Sharp

注意Post Sharp对商业应用已不再开源和免费。

以插件的形式支持如下aspects

以插件的形式集成了其他一些著名的框架,但是貌似是Post Sharp 1.5时的事儿了?

最重要的一点:只能inject本assembly中的东西,不能修改其他assembly,这就是为什么我的sample中其他都在调用Models.Tester,唯独Post'Sharp.Interception把Tester的实现copy了一份:要intercept的Buy方法的调用在Tester里。

 

LinFu

由于PostSharp已经收费,所以就想找一款同样是IL weaving方式的开源AOP框架。目前比较活跃的貌似就属LinFu了,但是实践起来却问题多多(单指AOP方面,这个框架还支持IoC、动态代理等)。比如

文档老旧,很多和最新版(2.3)对不上。

配置上(编辑csproj文件)不支持对指定方法的interception,这是最让人抓狂的,来看

   1: <PostWeaveTask TargetFile="$(MSBuildProjectDirectory)\$(OutputPath)$(MSBuildProjectName)$(TargetExt)"
   2:     InterceptAllExceptions="false"
   3:     InterceptAllFields="false"
   4:     InterceptAllNewInstances="false"
   5:     InterceptAllMethodCalls="true"
   6:     InterceptAllMethodBodies="false" />

要想inject指定的方法怎么办?实现自己的Task去解析!

另外,我的sample程序也跑不起来,原因不明。。。用MethodBodyReplacement的方法JIT时报错,用AroundMethodCall方法报CLR遇到一个invalid program。。。

 

评比

前人的一些工作

mono-cecil-vs-postsharp-core-vs-microsoft-cci-for-implementing-aop-framework

5 Sep 2009

Rating of Open Source AOP Frameworks in .NET

5 Aug 2008

主流.Net AOP Framework的功能定位分析

31 Aug 2005

其中第二篇参考意义比较大,感兴趣的不妨去看看,其结论是三甲为PostSharp、PIAB、Spring.Net

本文的评比标准

  • 能力上,至少能够inject public方法,不论其是否virtual
  • project本身至少要保持更新,且有release版
  • 性能与raw的调用不能差太多


参评框架

根据以上标准,选取PostSharp、PIAB、Spring.Net。

 

性能测试

参评的三个框架每种都用有源代码的情况下和无源代码的情况下两种方式实现,与raw调用进行对比。

测试case为IOder ICustomer.Buy(IProduct product)

每组都实现两个advice

  • 输出trace
  • 若Customer.WorkingAt = Product.ProducedBy,打八折

raw的实现是这样的

   1: public IOrder Buy(IProduct product, int count = 1)
   2: {
   3:     var order = new Order
   4:                     {
   5:                         Count = count,
   6:                         Product = product,
   7:                         Customer = this,
   8:                         UnitPrice = product.UnitPrice
   9:                     };
  10:  
  11:     if (WorkingAt.Equals(product.ProduceBy))
  12:     {
  13:         order.UnitPrice = product.UnitPrice*0.8;
  14:     }
  15:     Console.WriteLine(string.Format("{0} bought {1} at {2}. Count={3}, Unit Price={4}",
  16:                                     order.Customer.Name, order.Product.Name, DateTime.Now, order.Count,
  17:                                     order.UnitPrice));
  18:     return order;
  19: }

效果如下

image

测试调用100,200,…1500次,测算每次调用耗费的毫秒数,列表如下

 

100

200

300

400

500

600

700

800

900

1000

1100

1200

1300

1400

1500

Raw

2.33

0.465

0.56

0.55

0.548

0.558333

0.56

0.57125

0.564444

0.548

0.551818

0.55

0.548462

0.552857

0.548

PIAB

2.5

0.665

0.636667

0.6725

0.656

0.651667

0.65

0.6525

0.658889

0.654

0.645455

0.655

0.653077

0.655714

0.652

PIAB.Interception

2.5

0.67

0.66

0.6575

0.652

0.655

0.655714

0.64875

0.655556

0.654

0.654545

0.655833

0.653846

0.655

0.655333

PostSharp

3

0.55

0.553333

0.54

0.55

0.546667

0.548571

0.5575

0.553333

0.553

0.55

0.5525

0.552308

0.555

0.55

PostSharp.Interception

3.15

0.525

0.53

0.5725

0.554

0.561667

0.567143

0.555

0.565556

0.555

0.563636

0.558333

0.56

0.560714

0.558667

Spring

2.44

0.53

0.586667

0.5825

0.578

0.583333

0.574286

0.56375

0.56

0.564

0.553636

0.565

0.563846

0.564286

0.562667

Spring.Interception

2.59

0.52

0.523333

0.5375

0.538

0.548333

0.548571

0.54875

0.547778

0.544

0.553636

0.5475

0.546154

0.547143

0.549333

 

直观起见,直接上图(去掉了100的那一组数据)

image

可见PostSharp和Spring.Net都达到了接近Raw的性能,而PIAB仍需努力。

 

结论

如果喜欢IL织入,选择PostSharp

如果喜欢runtime proxy,选择Spring.Net

 

最佳实践

最佳实践这个题目对于我来说太大了。只能说是一些网上看来和实践得到的注意事项吧

通用

使用功能上最低限度的advice。例如只在after invoke做一些事的话就尽量不用around invoke的advice。

filed的interception是对OO的侵犯(直接无视了OO的第一条:封装),不要使用(三者中只有PostSharp支持)。

如果你的advice实现了某些特殊功能(而不是logging这种普通的功能),在你的方法上做出说明。

如果你的advice准备对全局起作用,就把它放在一个全局的地方,并且详加说明。

使用AOP时总是会造成一些与预期不同的结果,给你造成很多困扰。如果今后别人接手会给他造成更多的麻烦。所以有时还是用传统的方式吧。

PIAB

我使用EntLibConfig.NET4-32.exe编辑我的app.config,结果怎么都不行,之后删除了parameter节点中的typeName的assembly信息部分就OK了,原因不明。

PostSharp

已经不需要手动修改csproj文件,至少我的例子中没有。

Spring.Net

由于Spring.Net没有像PIAB一样提供一个Wrap方法,所以为了能从Spring容器中取出inject过的对象,还是需要在配置文件中进行配置,然后从ContextRegistry中取。例如本例中的

   1: <object id="customer" type="AopInvestigate.Implements.Spring.Models.Customer, AopInvestigate.Implements.Spring" singleton="false"/>

 

代码下载

最后给出示例代码

关于其中使用的各框架的license,上面那个表格里都有,这里就不再声明一次了。。。

posted @ 2011-02-11 13:52  jiaxingseng  阅读(8624)  评论(14编辑  收藏  举报