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. 安装Spring报错An error occurred while collecting items to be installed

    原因主要是eclipse和spring版本之间的匹配问题. An error occurred while collecting items to be installed session conte ...

  2. SQL 测验

    1.SQL 指的是? 您的回答:Structured Query Language 2.哪个 SQL 语句用于从数据库中提取数据? 您的回答:SELECT 3.哪条 SQL 语句用于更新数据库中的数据 ...

  3. UICollectionView 数据库元素分组 多种section分开显示

    第一遍 复杂方法 : 数据库查询一个表中userID 然后进行分类 中间去重 获得ID个数  放到section 中  显示 #pragma mark -  查询不同的ID 各数  - (void)c ...

  4. MpVue开发之组件引入的问题

    再一次开发订餐系统中,遇到订餐页面引入头部商品列表,底部组件时,报错,模块编译失败,未进入缓存区. 我以为是新添加的模块没有重新启动导致的,然后重新与运行npm run dev后还是失败, 最后经过反 ...

  5. php pdo调用SQLServer存储过程无法获取返回结果

    确定存储过程写的没问题,php调用后,跟踪了语句,也是没问题,就是获取不到返回结果.折腾,搞定. 较之前明确了1. 调用存储过程传参的写法: 2. 获取返回结果集的方法 参考: http://blog ...

  6. try...except包含try...finally方法

    def f(): try: try: f = open(raw_input('>')) print f.readlines() finally: f.close() #1/0 except Ex ...

  7. kmp学习小结

    KMP 简要说明 \(kmp\)是一个非常神奇的东西.它的\(fail(next)\)数组\(f[i]\)就表示\(1\)~\(i\)这个串的最长公共前缀后缀长度.根据这个\(fail\)数组,在匹配 ...

  8. atom的设置

    1.隐藏Keybinding Resolver Packages->Keybinding Resolver->Toggle.

  9. jdk1.8新特性之函数式接口

    函数式接口就是只有一个抽象方法的接口.如果这个接口里没有或者包含了两个以上的抽象方法,对不起,你不叫函数式接口,只能叫你接口.那这个函数式有啥用呢?如果配合Lambda表达式的话,可以大大的简化代码. ...

  10. Micro-PaaS(Docker+K8S)

    1.概述 Docker是一种Linux容器工具集,它是为构建(Build).交付(Ship)和运行(Run)分布式应用而设计的. Kubernates:是开源的容器集群管理系统.它构建在Docker技 ...