AOP编程

Aspect Oriented Programming(AOP),面向切面编程。AOP主要解决的问题是针对业务处理过程中对一些逻辑进行切面提取,它可以分散在处理过程中的不同的阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这样做可以提高程序的可重用性,同时提高了开发的效率。AOP编程一般会分离应用中的业务逻辑和通用系统级服务逻辑,可以让各自业务进行高内聚的开发,通用系统级服务也能得到很好的复用。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责其它的系统级关注点,例如日志或事务支持。AOP编程的主要场景是从业务逻辑里面提取日志记录,性能统计,安全控制,事务处理,异常处理等逻辑到独立的单元里。让负责业务逻辑的代码更加清晰和简单,从而更加容易维护,并且容易被复用。用一张图来看一下AOP编程的表现形式:

各种业务场景最终都要回归代码实现,从代码角度AOP编程应该需要实现的地方有:方法,异常,属性和字段,事件等进行拦截操作。还可以对程序集的元数据进行编程操作。现在我们使用PostSharp类库进行实现上面的功能。添加包:PostSharp。

方法拦截1:OnMethodBoundaryAspect

[PSerializable]
public class MyMethodBoundaryAspect : OnMethodBoundaryAspect
{
  public override void OnEntry(MethodExecutionArgs args)
  {
    //直接返回运行的方法并设置方法返回值
    //args.FlowBehavior = FlowBehavior.Return; 
    //args.ReturnValue = "hello";

    //设置MethodExecutionTag可以让不同时刻之间共享状态
    args.MethodExecutionTag = Stopwatch.StartNew();

    Console.WriteLine("Entry method name is " + args.Method.Name);
  }

  public override void OnSuccess(MethodExecutionArgs args)
  {
    Console.WriteLine("Success method name is " + args.Method.Name);
  }

  public override void OnException(MethodExecutionArgs args)
  {
    Console.WriteLine("Exception is " + args.Exception.Message);

    //这个是默认行为
    //args.FlowBehavior = FlowBehavior.RethrowException;

    //默认会再次抛出异常,可以设置继续执行,这里和FlowBehavior.Return逻辑一致。
    //args.FlowBehavior = FlowBehavior.Continue;

    //可以对异常捕获之后,可以隐藏原始异常,为了更加友好提示用户,可以抛出自定义异常。
    //args.Exception = new CustomArrayIndexException("这是从某个方面引发的", args.Exception);
    //args.FlowBehavior = FlowBehavior.ThrowException;
  }

  public override void OnExit(MethodExecutionArgs args)
  {
    var sw = (Stopwatch)args.MethodExecutionTag;
    sw.Stop();

    Console.WriteLine("Exit method name is " + args.Method.Name + " during time is " + sw.ElapsedMilliseconds / 1000 + " seconds.");
  }

  /// <summary>
  /// 异步挂起时触发
  /// 当await运算进入等待时触发
  /// </summary>
  /// <param name="args"></param>
  public override void OnYield(MethodExecutionArgs args)
  {
    var sw = (Stopwatch)args.MethodExecutionTag;
    sw.Start();
  }

  /// <summary>
  /// 异步完成时触发
  /// 当await运算完成时触发
  /// </summary>
  /// <param name="args"></param>
  public override void OnResume(MethodExecutionArgs args)
  {
    var sw = (Stopwatch)args.MethodExecutionTag;
    sw.Stop();
    Console.WriteLine("Async during time is " + sw.ElapsedMilliseconds / 1000 + " seconds.");
    sw.Start();
  }
}

方法拦截2:MethodInterceptionAspect

[PSerializable]
public class MyMethodInterceptionAspect : MethodInterceptionAspect
{
  public override void OnInvoke(MethodInterceptionArgs args)
  {
    Console.WriteLine("MyMethodInterception 1.");
    try
    {
      //目标方法会真正开始执行,如果方法内部报错,可以在这里捕获异常
      args.Proceed();
    }
    catch { }
    Console.WriteLine("MyMethodInterception 2.");
  }

  public override async Task OnInvokeAsync(MethodInterceptionArgs args)
  {
    Console.WriteLine("MyMethodInterception Async1.");

    try
    {
      await args.ProceedAsync();
    }
    catch (Exception ex)
    {
      Console.WriteLine(ex.Message);
      args.ReturnValue = "abc";
    }

    Console.WriteLine("MyMethodInterception Async2.");
  }
}

异常处理的拦截:OnExceptionAspect。向代码添加异常处理程序需要添加try/catch语句。这种方式实现的异常处理也是不可重用的,需要在必须处理异常的地方反复实现相同的逻辑。原始异常也会出现神秘的信息,并且经常会向用户暴露太多信息。PostSharp通过允许将自定义异常处理逻辑封装到可重用的类中来提供这些问题的解决方案,然后可以将其作为属性轻松应用于要处理异常的所有方法和属性。

[PSerializable]
public class MyExceptionAspect : OnExceptionAspect
{
  Type _type;

  /// <summary>
  /// type 可以用来指定特定类型的Exception,OnException就会捕获指定的Exception。
  /// 如果不指定,OnException会捕获所有的OnException。
  /// 如果只是想捕获一组Exception,可以指定这一组Exception的一个基类,
  /// 然后在OnException中动态的处理每一种Exception
  /// </summary>
  /// <param name="type"></param>
  public MyExceptionAspect(Type type)
  {
    _type = type;
  }

  public override Type GetExceptionType(MethodBase targetMethod)
  {
    return _type;
  }

  public override void OnException(MethodExecutionArgs args)
  {
    Console.WriteLine(args.Exception.Message);

    //默认会再次抛出异常,可以设置忽略异常,从目标方法直接返回并设置方法的返回值。
    //args.FlowBehavior = FlowBehavior.Return;
    //args.ReturnValue = "Hello";

    //可以对异常捕获之后,可以隐藏原始异常,为了更加友好提示用户,可以抛出自定义异常。
    //args.Exception = new CustomArrayIndexException("这是从某个方面引发的", args.Exception);
    //args.FlowBehavior = FlowBehavior.ThrowException;

  }
}

属性和字段的拦截:LocationInterceptionAspect

[PSerializable]
public class MyLocationInterceptionAspect : LocationInterceptionAspect
{
  public override void OnGetValue(LocationInterceptionArgs args)
  {
    object o = args.GetCurrentValue();
    if (o == null)
    {
      args.SetNewValue("value not set");
    }

    base.OnGetValue(args);

    if (args.Value == null)
    {
      args.Value = "foo";
    }
  }

  public override void OnSetValue(LocationInterceptionArgs args)
  {
    if (args.Value == null)
    {
      args.Value = "Empty String";
    }

    string existingValue = (string)args.GetCurrentValue();
    if (string.IsNullOrWhiteSpace(existingValue))
    {
      Console.WriteLine("Property existing value is ''");
    }

    args.ProceedSetValue();
  }
}

测试这几个拦截的测试数据:

public class Test
{
  public Test()
  {
    MyTeName = null;
    this.Print();
    this.PrintAsync();
    this.GetStr();
    this.GetStrAsync();
  }

  [MyLocationInterceptionAspect]
  public string MyField;

  [MyLocationInterceptionAspect]
  public string MyTeName { get; set; }

  [MyMethodBoundaryAspect]
  public async Task<string> PrintAsync()
  {
    Console.WriteLine("Print is working 1.");
    //throw new Exception("Make an exception.");
    Console.WriteLine("Print is working 2.");

    await Task.Delay(3000);

    Thread.Sleep(2000);

    return "abc";
  }

  [MyExceptionAspect(typeof(IOException))]
  public string Print()
  {
    Console.WriteLine("Print is working.");
    throw new IOException("Make an exception.");
  }

  [MyMethodInterceptionAspect]
  public string GetStr()
  {
    Console.WriteLine("GetStr is working.");
    throw new Exception("abc");
  }

  [MyMethodInterceptionAspect]
  public async Task<string> GetStrAsync()
  {
    Console.WriteLine("GetStrAsync is working.");
    await Task.Delay(3000);
    throw new Exception("Exception message abc");
  }
}

针对属性和字段的拦截还可以实现属性值发生变化及时通知的功能:

[PSerializable]
public class NotifyPropertyChangedAttribute : LocationInterceptionAspect
{
  public override void OnSetValue(LocationInterceptionArgs args)
  {
    if (args.Value != args.GetCurrentValue())
    {
      args.ProceedSetValue();
      ((Entity)args.Instance).OnPropertyChanged(args.Location.Name);
    }
  }
}

public class Entity : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  public void OnPropertyChanged(string propertyName)
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

public class Customer : Entity
{
  public Customer()
  {
    this.PropertyChanged += (sender, e) =>
    {
      var customer2 = (Customer)sender;
      string customerName = customer2.Name;
      Console.WriteLine("The changed property name is " + e.PropertyName + " ;value is " + customerName);
    };
    this.Name = "Nestor";
  }

  [NotifyPropertyChanged]
  public string Name { get; set; }
}

事件的拦截:

[PSerializable]
public class MyEventInterceptionAspect : EventInterceptionAspect
{
  public override void OnAddHandler(EventInterceptionArgs args)
  {
    base.OnAddHandler(args);
    Console.WriteLine("A handler was added");
  }

  public override void OnRemoveHandler(EventInterceptionArgs args)
  {
    base.OnRemoveHandler(args);
    Console.WriteLine("A handler was removed");
  }

  public override void OnInvokeHandler(EventInterceptionArgs args)
  {
    try
    {
      base.OnInvokeHandler(args);
      Console.WriteLine("A handler was invoked");
    }
    //Any time an exception is thrown during event execution, 
    //the offending event handler will be unsubscribed from the event.
    catch (Exception e)
    {
      args.RemoveHandler(args.Handler);
      Console.WriteLine("Handler '{0}' invoked with arguments {1} failed with exception {2}."
        ,args.Handler.Method
        ,string.Join(", ", args.Arguments.Select(a => a == null ? "null" : a.ToString()))
        ,e.GetType().Name);
      throw;
    }
  }
}

public class Example
{
  public Example()
  {
    this.SomeEvent += (s, e) =>
    {
      Console.WriteLine("Calling SomeEvent");
    };
    this.OnSomeEvent();
  }

  [MyEventInterceptionAspect]
  public event EventHandler<EventArgs> SomeEvent;
  public void OnSomeEvent()
  {
    if (SomeEvent != null)
    {
      SomeEvent.Invoke(this, EventArgs.Empty);
    }
  }
}

对自定义特性,在编译时生成元数据:

1. 自动添加类的DataContract功能

public sealed class AutoDataContractAttribute : TypeLevelAspect, IAspectProvider
{
  // This method is called at build time and should just provide other aspects. 
  public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
  {
    Type targetType = (Type)targetElement;

    CustomAttributeIntroductionAspect dataContractAspect =
      new CustomAttributeIntroductionAspect(
        new ObjectConstruction(typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes)));
    CustomAttributeIntroductionAspect dataMemberAspect =
      new CustomAttributeIntroductionAspect(
        new ObjectConstruction(typeof(DataMemberAttribute).GetConstructor(Type.EmptyTypes)));

    // Add the DataContract attribute to the type. 
    yield return new AspectInstance(targetType, dataContractAspect);

    // Add a DataMember attribute to every relevant property. 
    foreach (PropertyInfo property in
      targetType.GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance))
    {
      if (property.CanWrite)
        yield return new AspectInstance(property, dataMemberAspect);
    }
  }
}

[AutoDataContract]
public class Product
{
  public int ID { get; set; }
  public string Name { get; set; }
  public int RevisionNumber { get; set; }
}

在编译完成之后,Product类已经成员就会自动添加DataContract和DataMember的特性。通过JustDecompile反编译看结果:

2. 复制源类型的自定义特性到目标类型上

public class CopyCustomAttributesFrom : TypeLevelAspect, IAspectProvider
{
  private Type sourceType;

  public CopyCustomAttributesFrom(Type srcType)
  {
    sourceType = srcType;
  }

  public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
  {
    Type targetClassType = (Type)targetElement;

    //loop thru each property in target
    foreach (PropertyInfo targetPropertyInfo in targetClassType.GetProperties())
    {
      PropertyInfo sourcePropertyInfo = sourceType.GetProperty(targetPropertyInfo.Name);

      //loop thru all custom attributes for the source property and copy to the target property
      foreach (CustomAttributeData customAttributeData in sourcePropertyInfo.GetCustomAttributesData())
      {
        //filter out attributes that aren’t DataAnnotations                 
        if (customAttributeData.AttributeType.Namespace.Equals("System.ComponentModel.DataAnnotations"))
        {
          CustomAttributeIntroductionAspect customAttributeIntroductionAspect =
            new CustomAttributeIntroductionAspect(new ObjectConstruction(customAttributeData));

          yield return new AspectInstance(targetPropertyInfo, customAttributeIntroductionAspect);
        }
      }
    }
  }
}
public class Product2
{
  [Required]
  [Editable(false)]
  public int Id { get; set; }

  [Required]
  [Display(Name = "The product's name")]
  public string Name { get; set; }
  public int RevisionNumber { get; set; }
}

[CopyCustomAttributesFrom(typeof(Product2))]
class ProductViewModel
{
  public int Id { get; set; }
  public string Name { get; set; }
  public int RevisionNumber { get; set; }
}

这样ProductViewModel也会拥有和Product2一样的特性:

还可以对程序集在编译时期添加元数据:

public sealed class AddBuildInfoAspect : AssemblyLevelAspect, IAspectProvider
{
  public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
  {
    Assembly assembly = (Assembly)targetElement;

    byte[] userNameData = Encoding.UTF8.GetBytes(
       assembly.FullName + " was compiled by: " + Environment.UserName + " compiled at " + DateTime.Now.ToString());
    ManagedResourceIntroductionAspect mria2 = new ManagedResourceIntroductionAspect("BuildUser", userNameData);

    yield return new AspectInstance(assembly, mria2);
  }
}

[assembly: AddBuildInfoAspect]

通过反编译可以看到生成的Resources元数据:

所以PostSharp几乎完成我们想要的一切。

本文章参考了PostSharp的官方文档:

https://doc.postsharp.net/simple-aspects

本文分享自微信公众号 - 明丰随笔(liumingfengwx2),作者:刘明丰

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Windows服务小结 1

    Windows 服务(即,以前的 NT 服务)使您能够创建在它们自己的 Windows 会话中可长时间运行的可执行应用程序。

    小蜜蜂
  • IOC编程

    2. 开放/封闭原则: 添加任何新行为,应该是扩展到新类中,而不应该直接修改原来运行良好的代码。

    小蜜蜂
  • 浅谈.Net反射 4

    System.Reflection命名空间下的Assembly类型,代表了一个程序集,并包含了关于程序集的信息。

    小蜜蜂
  • 事件系统的设计方法

    事件功能用过段时间,像用户登入登出,充值,完成任务等非常的方便,有时间看看源码学习,设计的时候比较复杂,但完成后使用很简单,选了创建角色事件走遍流程,下面是JA...

    深雾
  • 组合模式

    组合模式 组合模式(Composite Pattern)有时候又叫做部分-整体模式,允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的...

    xiangzhihong
  • .Net中如何操作IIS

    Net中实际上已经为我们在这方面做得很好了。FCL中提供了不少的类来帮助我们完成这项工作,让我们的开发工作变非常简单和快乐。编程控制IIS实际上很简单,和ASP...

    脑洞的蜂蜜
  • 欧拉函数

    如果对于任意两个正整数m和n,均有f(mn)=f(m)f(n),就称为完全积性函数。

    饶文津
  • POJ 刷题系列:3041. Asteroids

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/...

    用户1147447
  • 【Java】质因数

    瑞新
  • Spring------自动化装配Bean(一) 一、创建 CompactDisc接口和SgetPeppers实现类二、启用spring组件扫描三、编写测试类,并运行 四、补充说明

      CompactDisc接口方法为播放。SgtPeppers实现CompactDisc接口。

    用户2417870

扫码关注云+社区

领取腾讯云代金券