AOP(面向切面编程:Aspect Oriented Programming)为诸如日志记录、性能统计、安全控制、事务处理、异常处理等与具体业务逻辑无关,却需要在全局范围进行执行的功能提供了一种良好重用和与业务逻辑解耦的实现思路。AOP思想是围绕着切面进行的,所谓“切面”就是目标对象的某种操作,其基本过程是在系统其它部分调用目标对象的某种操作时拦截这些调用,在进行真正的调用前/后执行一段中间逻辑,并根据中间逻辑的执行结果决定是否进行真实调用或者修改返回结果。

  AOP带来的好处是明显,但是我们怎么在项目中应用AOP呢?目前AOP在Java领域有一些较成熟的框架诸如 AspectJ、Spring AOP 等,在.NET领域有AspectC#、Castle等。尽管有现成的框架可以使用,但是由于存在学习曲线问题和框架的成熟度问题,使得很多项目通常都不会贸然使用第三方的框架。如果我们自己能够根据项目需要按需设计自己的轻量AOP组件/框架,这就能够给我们的项目带来良好的伸缩性。

  包括现有的框架在内,AOP的实现方式通常被分为“静态织入”和“动态织入”两种。采用静态织入方式的框架是通过扩展编译器对代码的中间语言(IL)插入代码的方式实现对目标对象的调用拦截。动态织入方式在.NET中可以有两种实现:采用“装饰者模式”设计项目类库来实现;基于透明代理(TransparentProxy)/真实代理(RealProxy)来实现。下面就介绍动态织入的这两种实现。

  • 最简单的AOP实现:采用“装饰者模式”实现方法调用拦截

  

  上图是GoF装饰者模式的经典类图,此处的实现类似于上图,基本原理是在封装对象的创建过程,在创建类型的实例时并不返回类型的真正实例,而是返回一个包装了真实对象的"Decorator"。以下的代码展示了这一实现过程。

  首先是定义一个公共的接口 IDataObject:


/// <summary> /// 数据对象接口; /// </summary> public interface IDataObject { /// <summary> /// 计算结果; /// </summary>  int Compute(); }

  接下来定义充当"Decorator"的 DataObjectProxy :

DataObjectProxy

/// <summary> /// IDataObject 代理;实现对 IDataObject 操作的拦截; /// </summary> public class DataObjectProxy : IDataObject { private IDataObject _realObject; public DataObjectProxy(IDataObject realObject) { _realObject = realObject; }
/// <summary> /// 拦截对 Compute 方法的调用; /// </summary> /// <returns></returns> public int Compute() { DoSomethingBeforeCompute();
int result = _realObject.Compute();
DoSomethingAfterCompute(result);
return result; }
private void DoSomethingAfterCompute(int result) { Console.WriteLine("After Compute " + _realObject.ToString() + ". Result=" + result); }
private void DoSomethingBeforeCompute() { Console.WriteLine("Before Compute " + _realObject.ToString()); } }

  DataObjectProxy拦截了对真实对象的 Compute 方法的调用,在 Compute 前后输出控制台信息。

  定义真正的实现 DataObject

DataObject

/// <summary> /// 数据对象; /// </summary> public class DataObject : IDataObject { private int _parameter1; private int _parameter2;
/// <summary> /// 私有构造函数,封装DataObject的实例化过程; /// </summary> private DataObject() { }
/// <summary> /// 创建数据对象; /// </summary> public static IDataObject CreateDataObject(int p1, int p2) { //创建真实实例; DataObject realObject = new DataObject(); realObject._parameter1 = p1; realObject._parameter2 = p2;
//返回代理; return new DataObjectProxy(realObject); }
/// <summary> /// “计算”的实现; /// </summary> /// <returns></returns> public int Compute() { return -1; } }

  DataObject 通过将构造函数定义为 private 封装其实例化过程。要获得一个 IDataObject 的实例就必需通过静态方法 CreateDataObject 。而用DataObjectProxy代替DataObject的偷天换日的过程就是在 CreateDataObject 方法中完成的。

  以下的代码将展示这一拦截过程

public static void Main(string[] args) { IDataObject dtObj = DataObject.CreateDataObject(10, 6); int result = dtObj.Compute();
Console.ReadLine(); }

  代码在控制台中的输出如下:

  上面的输出表明我们针对 Compute 方法的调用像期望的那样被拦截了。

  但是我们看到这种AOP的实现方式有其局限性:首先得基于一个特定的接口进行定义,无法创建通用的 Proxy 对象;其次对每一个要拦截的方法都要进行编码实现,无法重用。这些局限使得这种实现方法只能在局部使用,如果在大范围的使用还是存在许多重复性的编码。以下介绍的基于透明代理(TransparentProxy)/真实代理(RealProxy)的实现将解决这些问题。

  • 基于透明代理(TransparentProxy)/真实代理(RealProxy)实现方法调用拦截

  真实代理和透明代理机制是由 .NET Remoting 提供的,此处所说的真实代理(RealProxy)特指 System.Runtime.Remoting.Proxies 命名空间中的 RealProxy 类型。关于透明代理和真实代理的机制及相关概念请参阅 MSDN 文档,在此就不再赘述,直接用代码来表达。

  下面基于真实代理(RealProxy)定义了一个通用的代理对象 AopProxy<T> :

通用代理:AopProxy

/// <summary> /// 通用的代理; /// </summary> /// <typeparam name="T"></typeparam> class AopProxy<T> : RealProxy { private T _realObject;
public AopProxy(T realObject) :base(typeof(T)) { _realObject = realObject; }
/// <summary> /// 拦截所有方法的调用; /// </summary> /// <param name="msg"></param> /// <returns></returns> public override IMessage Invoke(IMessage msg) { IMethodCallMessage callMsg = msg as IMethodCallMessage; //调用前拦截; BeforeInvoke(callMsg.MethodBase); try { //调用真实方法; object retValue = callMsg.MethodBase.Invoke(_realObject, callMsg.Args); return new ReturnMessage(retValue, callMsg.Args, callMsg.ArgCount - callMsg.InArgCount, callMsg.LogicalCallContext, callMsg); } catch (Exception ex) { return new ReturnMessage(ex, callMsg); } finally { //调用后处理; AfterInvoke(callMsg.MethodBase); } }
private void BeforeInvoke(MethodBase method) { Console.WriteLine("Before Invoke {0}::{1}", typeof(T).FullName, method.ToString()); }
private void AfterInvoke(MethodBase method) { Console.WriteLine("After Invoke {0}::{1}", typeof(T).FullName, method.ToString()); } }

  以上的代码 AopProxy<T> 是个泛型类型,泛型参数 T 是要拦截的对象的类型,AopProxy<T> 构造函数需要一个实现泛型参数的真实对象作为参数。

  我们为上面的 DataObject 添加一个新的工厂方法 CreateDataObject2 用通过 AopProxy<T> 创建透明代理,具体如下:

使用 AopProxy 创建透明代理

/// <summary> /// 创建数据对象; /// </summary> public static IDataObject CreateDataObjec2(int p1, int p2) { //创建真实实例; DataObject realObject = new DataObject(); realObject._parameter1 = p1; realObject._parameter2 = p2;
//创建真实代理; AopProxy<IDataObject> proxy = new AopProxy<IDataObject>(realObject);
//返回透明代理; return (IDataObject)proxy.GetTransparentProxy(); }

  修改前面的入口程序通过 CreateDataObject2 方法获得代理,如下:

public static void Main(string[] args) { IDataObject dtObj = DataObject.CreateDataObject2(10, 6); int result = dtObj.Compute();
Console.ReadLine(); }

  此次的输出如下:

  实现我们预期的结果:通过 AopProxy<T> 实现对不同类型的目标对象的调用拦截的复用,并且在 AopProxy<T> 成功拦截了所有的方法调用。

  至此似乎一切都很完美,但是千万别以为世上真的存在“完美”。基于RealProxy 的实现是有其局限性的。在上面的 AopProxy<T> 的实现中,在示例中类型参数 T 是 IDataObject 接口。那我们能不能用一个 Class 作为泛型参数呢?答案是可以,但这个 Class 必需是继承自 MarshalByRefObject 类型,否则在 new  AopProxy<T> 实例时会抛出异常。也就是说如果目标类型是 Interface,则可以是任意类型,如果是 Class,则必须继承自 MarshalByRefObject 。还好,这只是一个小小的限制,而且面向对象编程也提倡多用接口。

  此外,在 AopProxy<T> 的实现中创建返回结果的 ReturnMessage 的代码不同于 MSDN 中的示例,在MSDN的示例中 ReturnMessage 构造函数的 outArgs 、outArgCount 参数总是指定为 null 和 0 。实际上这种情况下,如果目标方法中包含有 out 、ref 类型的参数时,这些此参数的返回值将被忽略,这显然是不允许的。而正确的方法应如此示例。

  AOP的实现很简单吧。如果我们再进一步,利用自定义 Attribute 对类型的方法进行标记 Before 和 After 操作,在 AopProxy<T> 的 Invoke 方法中通过反射解析这些标记,或者是通过配置文件定义 Before 和 After 操作,则可以实现更加灵活和丰富的 AOP 功能。

AOP 动态织入的.NET实现的更多相关文章

  1. 【开源】.Net Aop(静态织入)框架 BSF.Aop

    BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...

  2. 30个类手写Spring核心原理之AOP代码织入(5)

    本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...

  3. .netCore 动态织入

    using Microsoft.Extensions.DependencyInjection; using System; using System.Reflection; namespace Aop ...

  4. 黑马Spring学习 AOP XML和注解配置 5种通知 切点切面通知织入

    业务类 package cn.itcast.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoin ...

  5. .NET静态代码织入——肉夹馍(Rougamo)

    肉夹馍是什么 肉夹馍通过静态代码织入方式实现AOP的组件..NET常用的AOP有Castle DynamicProxy.AspectCore等,以上两种AOP组件都是通过运行时生成一个代理类执行AOP ...

  6. .NET静态代码织入——肉夹馍(Rougamo) 发布1.1.0

    肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...

  7. .NET静态代码织入——肉夹馍(Rougamo) 发布1.2.0

    肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...

  8. AspectJ的拓展学习--织入顺序和通知参数指定

    前言: 其实spring的aop非常的强大, 因此研究一下AspectJ还是有必要, 而不是仅仅停留在初级的阶段. 比如spring的事务是基于aop来实现的, 如果不能深入的研究, 可能很多知识点, ...

  9. Java AOP (2) runtime weaving 【Java 切面编程 (2) 运行时织入】

    接上一篇 Java AOP (1) compile time weaving [Java 切面编程 (1) 编译期织入] Dynamic proxy   动态代理 Befor talking abou ...

随机推荐

  1. Recorder︱深度学习小数据集表现、优化(Active Learning)、标注集网络获取

    一.深度学习在小数据集的表现 深度学习在小数据集情况下获得好效果,可以从两个角度去解决: 1.降低偏差,图像平移等操作 2.降低方差,dropout.随机梯度下降 先来看看深度学习在小数据集上表现的具 ...

  2. arduino 编程基础

    0. setup() 与 loop() 函数 void setup(): // put your setup code here, to run once: 仅执行一次: void loop(): / ...

  3. 从小白开始学习iOS开发

    从事iOS开发不经意间,已经两年过去了,记得当初看到OC是,我根本就不知道那些个东西是什么?从开始学习开机,到一个个英文开始,为了能找到工作,愣是耐下心去坚持看下去. 现在回想起来,很是庆幸我没有放弃 ...

  4. Buildroot 使用默认配置

    /******************************************************************************** * Buildroot 使用默认配置 ...

  5. autoburn eMMC hacking

    #!/bin/sh # autoburn eMMC hacking # 说明: # 看一下富林的自动烧录的执行脚本原理. # # -- 深圳 龙华樟坑村 曾剑锋 # 创建sd卡挂载目录 if [ ! ...

  6. [转载][QT][SQL]sql学习记录5_sqlite视图(View)

    转载自:http://www.runoob.com/sqlite/sqlite-view.html SQLite 视图(View) 视图(View)只不过是通过相关的名称存储在数据库中的一个 SQLi ...

  7. 【测试工具】tcpdump + wireshark 抓包实践

    Tcpdump + Wireshark 抓包实践 工具介绍 Tcpdump 看到dump大家应该有所意识吧,就是下载数据,抓数据.tcpdump是linux下的一个抓取tcp包的命令 Usage: t ...

  8. [译]贵宾犬咬伤了TLS

    原文链接:https://community.qualys.com/blogs/securitylabs/2014/12/08/poodle-bites-tls 原文发表时间:2014.12.8 今天 ...

  9. Mysql按照字段值做分组行转列查询

    今天做个后台服务,有个需求是批量生成一批表的数据,如果用BulkInsert会提升很大一截提交效率,但是如果用循环构造提交的Datable,则算法开销太高,所以用这种查询批量查出符合格式的DataTa ...

  10. Maven系列(一)之初识Maven

    Maven是个啥? Maven主要服务于基于Java平台的项目构建.依赖管理和项目信息管理,并且Maven是跨平台的,这意味着无论是在Windows上,还是在Linux或者Mac上,都可以使用同样的命 ...