前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AOP编程

AOP编程

作者头像
小蜜蜂
发布2019-07-15 16:01:44
4810
发布2019-07-15 16:01:44
举报
文章被收录于专栏:明丰随笔明丰随笔明丰随笔

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 明丰随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档