出处:https://www.cnblogs.com/leoo2sk/archive/2010/11/30/aop-postsharp.html

摘要

本文首先介绍AOP(面向方面编程)的相关概念及理论,然后介绍如何使用PostSharp框架在.NET平台上实现AOP,最后对PostSharp的机制及AOP的优劣进行一个简单的分析。

AOP(Aspect-Oriented Programming)

AOP的基本定义及作用

根据维基百科的定义,“AOP(Aspect-Oriented Programming)是一种将函数的辅助性功能与业务逻辑相分离的编程泛型(programming paradigm),其目的是将横切关注点(cross-cutting concerns)分离出来,使得程序具有更高的模块化特性。AOP是面向方面软件开发(Aspect-Oriented Software Development)在编码实现层面上的具体表现(面向方面软件开发AOSD是一个囊括面向方面分析、面向方面设计和面向方面编程等一系列概念的完整工程系统——笔者注)。AOP包括编程模型和具体用于实现AOP的框架两部分。”

下面对上文提到的定义进行一些解释。

在当前大多数支持面向对象的编程语言中(例如C#,Java等),函数(Function)是表述程序功能的最小单元,而一个函数的代码层面往往同时含有核心业务逻辑和辅助性功能。核心业务逻辑指一个函数本身主要要实现的业务功能,例如在一个在线电子商务系统中,“PlaceOrder”函数其核心业务逻辑是“下订单”,而“UpgradeMember”函数其核心业务是“提升一个会员的等级”。但是,一个函数除了核心业务代码外,往往还会有一些辅助性功能代码,如事务处理、缓存处理、日志记录、异常处理等等。而这些辅助性功能一般会存在于大多数甚至所有业务函数中,即形成AOSD中所谓的横切关注点,如图1所示。

图1、横切关注点示意

横切关注点的存在,造成了如下几个问题。

  • 代码编写和维护困难。

横切关注点不仅横切各个函数,还可能在不同类甚至不同工程间横切,使得同一个辅助功能(如事务处理)分散到各处,如果要增加新函数时要时刻注意别忘了添加所有需要的横切代码。另外,如果需要对其进行修改,则需要到所有被横切的函数中修改,维护难度极大。

  • 引入大量冗余代码

由于同一个辅助性功能的代码几乎是完全相同的,这样就会令同样的代码在各个函数中出现,引入了大量冗余代码。

  • 降低代码质量

横切关注点令核心业务代码和辅助性代码杂糅纠缠在一起,破坏了业务函数代码的纯净性和函数职责的单一性,引入了大量繁杂的代码和结构,使得代码质量下降。

所以,AOP的核心思想就是在编写代码时将横切关注点分离出来,形成单独的模块,单独编写和维护,不再分散到各业务函数,使得业务函数仅包含核心业务代码,从而解决以上问题。而在程序编译或运行时,通过某些手段(下文介绍)令独立的横切关注点代码可以与核心业务代码自动协作运行,完成本身需要的功能。

AOP相关术语

方面(Aspect)

一个Aspect指上文提到的横切关注点在编程中的具体实现,它包含一个横切关注点所需要实现的具体辅助功能。具体到代码中,Aspect可能会被实现为一个Class,一个Function或一个Attribute。

连接点(Join Point)

连接点指一个业务函数代码中的一个位置或时机,在这个位置或时机允许Aspect代码插入执行。常见的连接点有进入函数执行业务代码前时、执行完全部业务代码离开函数前、当有异常发生在异常处理代码执行前等等。

织入(Weaving)

织入指将指定的Aspect代码插入指定连接点,使得横切代码与业务代码交合在一起。

连接模型(JPM, Join Point Model)

JPM主要是面向方面语言(如AspectJ)或面向方面框架的语义模型。主要包含以下三点:有哪些可用连接点,如何指定连接点以及如何织入。

AOP的实现方式

一般来说,在纯编译型语言(如C、C++)等语言中实现AOP非常困难,必须完全从编译器角度入手。本文主要讨论托管型语言(如C#,Java)中AOP的实现方式。AOP的主要实现方式有编译时AOP和运行时AOP两种,下面分别介绍。

编译时AOP(静态织入)

编译时AOP的实现思想是给语言的编译器做扩展,使得在编译程序的时候编译器将相应的Aspect代码织入到业务代码的指定连接点,输出整合的结果。图2是编译时AOP的示意图(以.NET平台为例)。

图2、编译时AOP示意图

如图2所示,当使用静态织入时,带AOP扩展的编译器会在编译时将Aspect代码织入业务函数代码,形成整合后的IL,然后交由CLR运行。

运行时AOP(动态织入)

运行时AOP如图3所示。

图3、运行时AOP的示意图

如图3所示,运行时AOP的实现方式是将扩展添加到运行虚拟机而不是编译器。Aspect和业务代码分别独立编译,而在运行时由虚拟机在必要时进行织入。

PostSharp

PostSharp简介

PostSharp是一个用于在.NET平台上实现AOP的框架,是我比较常用的一个AOP框架,官方网站为http://www.sharpcrafters.com。目前最新版本为2.0,但是2.0的license不再免费,因此个人建议下载1.5版,同时下文都是基于PostSharp1.5。

PostSharp使用静态织入方式实现AOP,其连接点非常丰富,使用简单,而且相对其它一些.NET平台上的AOP框架来说,PostSharp较为轻量级,但是功能却一点也不逊色,因此是我比较喜欢的一个AOP框架。更多关于PostSharp的介绍请参看其官方网站。

另外使用PostSharp与其它框架不太一样的是一定要下载安装包安装,只引用类库是不行的,因为上文说过,AOP框架需要为编译器或运行时添加扩展。

使用PostSharp实现AOP示例

这一节将通过一个例子演示如何使用PostSharp在.NET平台上实现AOP。这个例子将通过AOP为核心业务函数增加日志记录功能。

新建项目

首先新建一个C#的WinForm应用程序,如图4所示,这里将工程命名为“PostSharpExample”。

图4、新建项目

编写核心业务函数

首先我们来编写核心业务。当然这里不存在真正的业务,我们只是模拟一个而已。将要模拟的核心业务是预定房间。先构建一个如图5所示的简单UI。

图5、UI界面

下面我们为项目增加一个“CoreBusiness”类,并在其中添加“Subscribe”方法。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
using System;
 
namespace PostSharpExample
{
    public class CoreBusiness
    {
        public static void Describe(string memberName, string roomNumber)
        {
            System.Windows.Forms.MessageBox.Show(String.Format("尊敬的会员{0},恭喜您预定房间{1}成功!", memberName, roomNumber), "提示");
        }
    }
}

可以看到,这里Subscribe方法仅仅是输出一个提示框。当然,在真正项目中这种输出型代码不应该写在业务逻辑中,这里这样写主要是为了演示方便。然后,我们在Form1中调用Subscribe业务方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Windows.Forms;
 
namespace PostSharpExample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void BTN_SUBSCRIBE_Click(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(TXB_NAME.Text.Trim()) && !String.IsNullOrEmpty(TXB_ROOM.Text.Trim()))
                CoreBusiness.Describe(TXB_NAME.Text.Trim(), TXB_ROOM.Text.Trim());
            else
                MessageBox.Show("信息不完整","提示");
        }
    }
}

运行程序就可以看到相应的效果:

图6、预定房间成功演示效果

使用AOP增加日志记录功能

现在加入我们要为程序添加日志功能,记录业务函数的执行情况。这里我们假定需要将日志记录到纯文本文件中,首先我们完成日志记录工具类,LoggingHelper。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
using System.IO;
 
namespace PostSharpExample
{
    class LoggingHelper
    {
        private const String _errLogFilePath = @"log.txt";
 
        public static void Writelog(String message)
        {
            StreamWriter sw = new StreamWriter(_errLogFilePath, true);
            String logContent = String.Format("[{0}]{1}", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"), message);
            sw.WriteLine(logContent);
            sw.Flush();
            sw.Close();
        }
    }
}

如果不使用AOP,则我们要为包括Subscribe在内的每一个方法在核心业务代码的前后插入日志记录代码(Writelog),我们看看使用PostSharp如何将这种横切关注点分离出来。因为要使用PostSharp,所以要先添加对PostSharp库文件的引用,安装过PostSharp后,在系统可引用项中会多出“PostSharp.Laos”、“PostSharp.Public”和“PostSharp.AspNet”,这里我们做的是Winform程序,所以只需添加对“PostSharp.Laos”和“PostSharp.Public”的引用即可。

下面我们就要写Aspect了,PostSharp的Aspect是使用Attribute实现的,下面是我实现的日志记录Aspect代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using PostSharp.Laos;
 
namespace PostSharpExample
{
    [Serializable]
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public sealed class LoggingAttribute : OnMethodBoundaryAspect
    {
        public string BusinessName { get; set; }
 
        public override void OnEntry(MethodExecutionEventArgs eventArgs)
        {
            LoggingHelper.Writelog(BusinessName + "开始执行");
        }
 
        public override void OnExit(MethodExecutionEventArgs eventArgs)
        {
            LoggingHelper.Writelog(BusinessName + "成功完成");
        }
    }
}

我们约定每个Aspect类的命名必须为“XXXAttribute”的形式。其中“XXX”就是这个Aspect的名字。PostSharp中提供了丰富的内置“Base Aspect”以便我们继承,其中这里我们继承“OnMethodBoundaryAspect ”,这个Aspect提供了进入、退出函数等连接点方法。另外,Aspect上必须设置“[Serializable] ”,这与PostSharp内部对Aspect的生命周期管理有关,具体为什么请参看这里

我们的LoggingAttribute非常简单,就是在进入(Entry)和离开(Exit)函数时分别记录日志到log文件。现在我们把这个Aspect应用到业务方法上:

1
2
3
4
5
[Logging(BusinessName="预定房间")]
public static void Describe(string memberName, string roomNumber)
{
    System.Windows.Forms.MessageBox.Show(String.Format("尊敬的会员{0},恭喜您预定房间{1}成功!", memberName, roomNumber), "提示");
}

可以看到,应用Aspect非常简单,就是将相应的Attribute加到业务方法上面。现在我们再运行预定房间程序,结果和上次没什么两样,但是如果我们打开程序目录,会看到多了一个“log.txt”文件,里面记录有类似图7的内容。

图7、日志内容

可以看到,我们已经通过AOP实现了日志记录功能。通过AOP将横切关注点分离出来后,日志记录的代码都放在LoggingAttribute里,需要修改只要修改一处即可。同时,业务方法仅含有业务代码,这样大大提高了程序代码的可读性和可维护性。

对PostSharp运行机制的简要分析

上文已经说到,PostSharp使用的是静态织入技术,下面我们分析一下PostSharp是如何实现的。

首先,当安装PostSharp时,它自动为Visual Studio编译器添加了AOP扩展。如果仔细观察PostSharpExample编译信息,会发现有这么两行:

图8、PostSharp编译信息

很明显,在.NET Complier编译完成后,下面PostSharp又做了一部分工作,这部分工作就是静态织入的过程。如果我们用.NET Reflector查看PostSharpExample.exe中Subscribe方法的反编译代码,会发现多了很多东西:

图9、织入Aspect后的Describe代码(由.NET Reflector反编译)

这些多出来的代码,就是PostSharp静态织入进去的。当然,这些代码在每次编译完成后,PostSharp都会重新织入一次,所以整个过程对程序员是透明的,我们只需维护纯净的业务代码和Aspect代码即可。

使用PostSharp的优点和缺点(即使用AOP的优点和缺点)

总体来说,使用PostSharp,将会带来如下优点:

  • 横切关注点单独分离出来,提高了代码的清晰性和可维护性。
  • 只要在Aspect中编写辅助性功能代码,在一定程度上减少了工作量和冗余代码。

当然,使用PostSharp也不是没有缺点,主要缺点有如下两方面:

  • 增加了调试的难度。
  • 相比于不用AOP的代码,运行效率有所降低。

所以,对于是否引入AOP,请根据项目具体情况,权衡而定。

对于PostSharp的进一步学习

本文只是简要介绍了PostSharp以及实现了一个小例子,并不打算详细完整地介绍PostSharp的方方面面,而只想起到一个抛砖引玉的作用。PostSharp还有非常丰富的功能等待各位学习,因此,如果您对PostSharp十分有兴趣,想进一步学习,请参看PostSharp官方参考文档

相关下载

本文用到的Example请点击这里下载

PostSharp1.5安装包请点击这里下载

使用PostSharp在.NET平台上实现AOP(转)的更多相关文章

  1. 使用PostSharp在.NET平台上实现AOP

    摘自:http://www.cnblogs.com/leoo2sk/archive/2010/11/30/aop-postsharp.html 本文首先介绍AOP(面向方面编程)的相关概念及理论,然后 ...

  2. 【Win 10应用开发】如何知道UAP在哪个平台上运行

    面向22世纪的现代化应用程序可以同时在多种设备上运行,于是有朋友会有一个疑问:有时候,我们还真的需要判断一下,UAP应用程序在哪个平台上运行.尽管大多情况下我们不必要这样做,但某些特殊情况还得考虑.比 ...

  3. 关于如何在Android、Java等非微软平台上建立高信任的SharePoint应用程序

    关于如何在非微软平台上建立高信任的SharePoint应用程序 原文 :http://blogs.msdn.com/b/kaevans/archive/2014/07/14/high-trust-sh ...

  4. JVM 平台上的各种语言的开发指南

    JVM 平台上的各种语言的开发指南 为什么我们需要如此多的JVM语言? 在2013年你可以有50中JVM语言的选择来用于你的下一个项目.尽管你可以说出一大打的名字,你会准备为你的下一个项目选择一种新的 ...

  5. 在Windows平台上安装Node.js及NPM模块管理

    1. 下载Node.js官方Windows版程序:http://nodejs.org/#download    从0.6.1开始,Node.js在Windows平台上提供了两种安装方式,一是.MSI安 ...

  6. MTK 平台上查询当前使用的摄像头模组及所支持预览分辨率

    1,MTK 平台如何查询当前使用的是哪颗摄像头及相关的模组信息? 在该目录下可以查到当前平台及相关项目的配置文件 ProjectConfig.mk \ALPS.JB.MP.V1_W_20120919\ ...

  7. [Linux] Ubuntu Server 12.04 LTS 平台上搭建WordPress(Nginx+MySql+PHP) Part II

    接着上一节继续搭建我们的LNMP平台,接下来我们安装PHP相关的服务 sudo apt-get install php5-cli php5-cgi php5-fpm php5-mcrypt php5- ...

  8. [Linux] Ubuntu Server 12.04 LTS 平台上搭建WordPress(Nginx+MySQL+PHP) Part IV

    接下来我们去下载 WorePress 用最新的 3.7.1 下载地址是:http://cn.wordpress.org/wordpress-3.7.1-zh_CN.zip 我们先建立一个文件夹 /va ...

  9. 随笔之Android平台上的进程调度探讨

    http://blog.csdn.net/innost/article/details/6940136 随笔之Android平台上的进程调度探讨 一由来 最近在翻阅MediaProvider的时候,突 ...

随机推荐

  1. laravel中的模型关联之(一对多)

    一对多 一对多就相当于,一个用户有多篇文章,这多篇文章都对应一个用户 这是一张文章表,一个用户有多篇文章,这里是在用户模型里面获取用户的所有文章, 第二个参数就是获取的模型文章表(post)里面的用户 ...

  2. How to set the bash display to not show the vim text after exit?

    Xshell客户端在vim编辑文件保存退出,仍然显示文本内容,而不是回到shell terminal终端. 解决办法如下: User1 is using TERM=xterm, in this cas ...

  3. TZOJ 3244 Happy YuYu's Birthday(数学几何)

    描述 9月10日教师节,也是YuYu的生日,妈妈给YuYu准备了一个很大的圆形蛋糕,YuYu看中了蛋糕中间那诱人的樱桃(都挤到一块啦),小家伙很高兴,心里开始盘算着如何将樱桃全部分给自己.YuYu是个 ...

  4. Win7系统不能记忆窗口大小与位置解决方法

    似在某此系统优化后,无意发现系统在注销或重启后,打开资源管理器,它以默认大小及位置显示. 对于习惯自定义操作来说,甚为不便,遍找方法未有奏效者,但总萦绕心头,时时记起. 今日再找问题解决方法,难兄难弟 ...

  5. 【网络编程三】网络通信之多进程(线程)实现TCP通信(一)

    [多进程] [多线程] /************************************************************************* > File sum ...

  6. ubuntu下安装redis及常用操作

    reids是一个内存数据库,具有存取速度快,使用简单等优点.我们常常在分布式或者负载均衡的系统中使用它来缓存一些公用的且不是很大量的数据,比如session或者各类token(比如微信的access_ ...

  7. [leetcode]694. Number of Distinct Islands你究竟有几个异小岛?

    Given a non-empty 2D array grid of 0's and 1's, an island is a group of 1's (representing land) conn ...

  8. 6-完美解决Error:SSL peer shut down incorrectly

    转载自: 完美解决Error:SSL peer shut down incorrectly 打开gradle文件夹下的gradle-wrapper文件 修改其中的配置文件将红色区域修改为http:// ...

  9. @params、@PathVariabl和@RequestParam用法与区别

    [1]params params: 指定request中必须包含某些参数值是,才让该方法处理. @RequestMapping(value = "testParamsAndHeaders&q ...

  10. centos 命令学习

    关机&重启 shutdown -h 10          #计算机将于10分钟后关闭,且会显示在登录用户的当前屏幕中 shutdown -h now       #计算机会立刻关机 shut ...