出处: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. 在OpenShift平台上验证NVIDIA DGX系统的分布式多节点自动驾驶AI训练

    在OpenShift平台上验证NVIDIA DGX系统的分布式多节点自动驾驶AI训练 自动驾驶汽车的深度神经网络(DNN)开发是一项艰巨的工作.本文验证了DGX多节点,多GPU,分布式训练在DXC机器 ...

  2. 用matlab怎么画视电阻率拟断面图,在MATLAB平台上实现可控源音频大地电磁反演数据三维可视化显示...

    第29卷 增刊 物探化探计算技术 2007年10月 收稿日期6文章编号:1001-1749(2007)增刊(1)-0068-04 在MAT LAB 平台上实现可控源音频大地 电磁反演数据三维可视化显示 ...

  3. linux平台调试终端,10款Linux平台上优秀的调试器,总有一款适合你!

    前言 调试器对于检测程序中的 bug 是必不可少的.有很多优秀的 Linux 调试器,可以很容易地找到应用程序中的错误.我们将在本文中介绍几款好用的调试器. 1. GNU Debugger (GDB) ...

  4. 如何在SAP云平台上使用MongoDB服务

    首先按照我这篇文章在SAP云平台上给您的账号分配MongboDB服务:如何在SAP云平台的Cloud Foundry环境下添加新的Service 然后从这个链接下载SAP提供的例子程序. 1. 使用命 ...

  5. 虚拟机VMWare“提示:软件虚拟化与此平台上的长模式不兼容”的解决方法

    虚拟机VMWare"提示:软件虚拟化与此平台上的长模式不兼容" 不少童鞋反映,在使用Windows7 64位操作系统时,无法运行VMWare或MS Virtual server等软 ...

  6. Pentaho平台上加仪表盘插件步骤

    Pentaho平台上加仪表盘插件步骤 下面我将最近学习的在Pentaho平台上加仪表盘插件的步骤稍微整理了一下,希望对有需要的朋友有些帮助. 1. 解压插件包:cdf-de_0.2.tar.bz2(这 ...

  7. 在linux上装git教程,教你玩转Git-Linux 平台上安装

    导读 Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目.Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件.Gi ...

  8. GNU/Linux平台上正则表达式的简单使用

    友情提醒:本博文涉及的内容中涉及到的系统实践操作在Centos6.5上实现,GNU/Linux简称为linux,GNU/grep简称为grep,GNU/sed简称为sed,GNU/gawk简称为awk ...

  9. 在C++平台上部署PyTorch模型流程+踩坑实录

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 导读 本文主要讲解如何将pytorch的模型部署到c++平台上的模 ...

  10. x264代码剖析(一):图文详解x264在Windows平台上的搭建

    x264代码剖析(一):图文详解x264在Windows平台上的搭建 X264源码下载地址:http://ftp.videolan.org/pub/videolan/x264/ 平台:win7 PC. ...

最新文章

  1. 鸿海拟将相关物流企业在中国大陆上市
  2. 全球及中国苯基异丙基聚二甲基硅氧烷市场需求调查与竞争格局策略报告2022版
  3. SQLite B+树实现代码
  4. 几篇关于Hadoop+Hive数据仓库的入门文章
  5. 数据中心机房设计及各专业技术平衡
  6. 与TCP/IP协议的初次见面(一)
  7. java教程百度文库,成功拿到offer
  8. 技能学习重要的古语_学习方法:最重要的开发人员技能
  9. idea编译android模块,Android IntelliJ IDEA 14.1:模块没有主类
  10. js中 NaN、 null 及 undefined 的区别(转载)
  11. [翻译]CSS变量让你轻松制作响应式网页
  12. expdp/impdb数据泵导入导出数据
  13. sublime 格式化Json
  14. 开启文案或软文编辑的思考:60后大哥靠一篇软文年入500万案例解析
  15. 写字机器人软件_被误解的写字机器人应该如何为自己正名?
  16. 2018ICPC徐州赛区网络预赛
  17. 以太坊系列---ipc实现方式---管道通信库npipe
  18. 机器学习历程——人工智能基础与应用导论(8)(神经网络——感知机模型)
  19. python绘图库turtle_Python库-----Turtle绘图库
  20. sqlserver存储过程 返回临时表问题

热门文章

  1. android应用程序优化之布局优化
  2. 问题的分析与解决(培训总结)
  3. 想要导航提示页_如何优化网站导航呢?
  4. linux 安装_linux 安装 powershell
  5. 服务器的作用及用途,服务器的作用和用途是什么
  6. 计算机中丢失profapi,profapi.dll
  7. ide在控制台输入编译命令_编译原理、VC的构成以及用VC2010建立C程序
  8. python 画频率分布直方图求平均数_Python绘制频率分布直方图的示例
  9. python怎么打出pi_随手写了段 Python,微信的地球居然转起来
  10. labimage 怎样旋转图片_西安匠艺工坊丨别克gl8内饰改装床车图片,精湛工艺,空间力量...