zoukankan      html  css  js  c++  java
  • PostSharp摘要

    摘要

    本文首先介绍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所示。

    image

    图1、横切关注点示意

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

    • 代码编写和维护困难。

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

    • 引入大量冗余代码

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

    • 降低代码质量

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

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

    AOP相关术语

    方面(Aspect)

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

    连接点(Join Point)

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

    织入(Point Cut)

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

    连接模型(JPM, Join Point Model)

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

    AOP的实现方式

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

    编译时AOP(静态织入)

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

    image

    图2、编译时AOP示意图

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

    运行时AOP(动态织入)

    运行时AOP如图3所示。image

    图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”。

    image

    图4、新建项目

    编写核心业务函数

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

    image

    图5、UI界面

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

    01 using System;
    02   
    03 namespace PostSharpExample
    04 {
    05     public class CoreBusiness
    06     {
    07         public static void Describe(string memberName, string roomNumber)
    08         {
    09             System.Windows.Forms.MessageBox.Show(String.Format("尊敬的会员{0},恭喜您预定房间{1}成功!", memberName, roomNumber), "提示");
    10         }
    11     }
    12 }

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

    01 using System;
    02 using System.Windows.Forms;
    03   
    04 namespace PostSharpExample
    05 {
    06     public partial class Form1 : Form
    07     {
    08         public Form1()
    09         {
    10             InitializeComponent();
    11         }
    12   
    13         private void BTN_SUBSCRIBE_Click(object sender, EventArgs e)
    14         {
    15             if (!String.IsNullOrEmpty(TXB_NAME.Text.Trim()) && !String.IsNullOrEmpty(TXB_ROOM.Text.Trim()))
    16                 CoreBusiness.Describe(TXB_NAME.Text.Trim(), TXB_ROOM.Text.Trim());
    17             else
    18                 MessageBox.Show("信息不完整","提示");
    19         }
    20     }
    21 }

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

    image

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

    使用AOP增加日志记录功能

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

    01 using System;
    02 using System.IO;
    03   
    04 namespace PostSharpExample
    05 {
    06     class LoggingHelper
    07     {
    08         private const String _errLogFilePath = @"log.txt";
    09   
    10         public static void Writelog(String message)
    11         {
    12             StreamWriter sw = new StreamWriter(_errLogFilePath, true);
    13             String logContent = String.Format("[{0}]{1}", DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"), message);
    14             sw.WriteLine(logContent);
    15             sw.Flush();
    16             sw.Close();
    17         }
    18     }
    19 }

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

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

    01 using System;
    02 using PostSharp.Laos;
    03   
    04 namespace PostSharpExample
    05 {
    06     [Serializable]
    07     [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    08     public sealed class LoggingAttribute : OnMethodBoundaryAspect
    09     {
    10         public string BusinessName { get; set; }
    11   
    12         public override void OnEntry(MethodExecutionEventArgs eventArgs)
    13         {
    14             LoggingHelper.Writelog(BusinessName + "开始执行");
    15         }
    16   
    17         public override void OnExit(MethodExecutionEventArgs eventArgs)
    18         {
    19             LoggingHelper.Writelog(BusinessName + "成功完成");
    20         }
    21     }
    22 }

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

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

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

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

    image

    图7、日志内容

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

    对PostSharp运行机制的简要分析

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

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

    image

    图8、PostSharp编译信息

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

    image

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

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

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

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

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

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

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

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

    对于PostSharp的进一步学习

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

    示例程序下载

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

    Creative Commons License 本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载或演绎,但是必须保留本文的署名张洋(包含链接),且不得用于商业用途。如您有任何疑问或者授权方面的协商,请与我联系
  • 相关阅读:
    Java核心技术 卷一 笔记四 库类的直接使用
    Java核心技术 卷一 笔记三 大数值及数组
    Java核心技术 卷一 笔记2 字符串的复制
    Java核心技术 卷一 笔记1
    修改css 样式后, hover事件 不生效
    修改 element ui input 输入框 样式不生效问题
    css3 计算属性
    Vue3 改动系列
    浏览器实现,向下滑动 鼠标滚轮,页面横向移动
    linux ceont0s7 vue 打包压缩图片 一直报错
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/1892564.html
Copyright © 2011-2022 走看看