C# Advanced Tutorial 2-2-AOP


主要内容概要

1 AOP面向切面编程
2 静态AOP实现
3 动态代理AOP实现
4 静态织入实现AOP
5 Unity 依赖注入容器的AOP
6 MVC中的Filter 标记特性,然后该方法执行前后就多了逻辑,Invoker调用中心负责反射调用方法,检查特性,有则执行额外逻辑。

POP和OOP

  • POP:面向过程编程,代码是线性式 严格按照顺序

  • OOP:面向对象编程 万物皆对象,可以搭建大型的复杂系统
    砖块—墙—房间—高楼大厦(稳定) 砖块确实是稳定,静态的
    类–各种功能点—功能模块—系统(平台) 类是稳定的吗?
    类是无法一直稳定,确实有场景需要改动。。。
    GOF 23种设计模式 面对变化,核心套路都是依赖抽象 可以更换具体的类型 (最小的粒度只能到类)
    只能替换整个对象,不能让类动态话,只能替换类;

OOP

AOP

AOP: 可以让类动态化!动态粒度更小,可以把类里面的成员给修改掉!
AOP 允许开发者动态的修改静态的OO模型,就像现实生活中对象在生命周期中会不断的改变自身。
AOP 面向切面编程 (Aspect Oriented Programming) 其实是OOP扩展。

使用Aop特点以后:

  • 只聚焦于自身模块的业务功能,用户验证/日志处理/都可以通过Aop给动态加进来!程序设计变得简单。
  • 功能扩展性更强,把功能集中管理,代码复用,更加规范

AOP

AOP的多种方式

是按AOP的方式:

  1. 装饰器模式/代理模式 静态实现
  2. 动态实现 (Remoting)(Emit)
  3. 通过Unity 支持AOP

下面会结合一个例子去描述这几种方式的具体实现,有一个用户的接口和实现,描述如下:

 public interface IUserProcessor
{
  void RegUser(User user);
}

public class UserProcessor : IUserProcessor
{
  public void RegUser(User user)
  {
    Console.WriteLine("用户已注册。Name:{0},PassWord:{1}", user.Name, user.Password);
  }
}

现在要想扩展这个实现,但是又不能去修改原来的实现,应该如何实现呢?例如在注册之前和之后都要加上一些操作。

// 业务逻辑之前
private void BeforeProceed(User user)
{
  Console.WriteLine("方法执行前");
}

// 业务逻辑之后
private void AfterProceed(User user)
{
  Console.WriteLine("方法执行后");
}

装饰器模式

装饰器的模式去提供一个AOP功能:

public class UserProcessorDecorator : IUserProcessor
{
  private IUserProcessor _UserProcessor { get; set; }
  public UserProcessorDecorator(IUserProcessor userprocessor)
  {
    this._UserProcessor = userprocessor;
  }

  public void RegUser(User user)
  {
    BeforeProceed(user);
    this._UserProcessor.RegUser(user);
    AfterProceed(user);
  }
}

使用的时候这么用:

User user = new User()
{
  Name = "Richard",
  Password = "123123123123"
};
IUserProcessor processor = new UserProcessor();
processor.RegUser(user);
Console.WriteLine("***************");

processor = new UserProcessorDecorator(processor);
processor.RegUser(user);

代理模式

代理模式去提供一个AOP功能:

public class ProxyUserProcessor : IUserProcessor
{
  private IUserProcessor _UserProcessor = new UserProcessor();
  public void RegUser(User user)
  {
    BeforeProceed(user);
    this._UserProcessor.RegUser(user);
    AfterProceed(user);
  }
}

使用的时候可以这么用:

User user = new User()
{
  Name = "Richard",
  Password = "123123123123"
};
IUserProcessor processor = new UserProcessor();
processor.RegUser(user);
Console.WriteLine("***************");

processor = new ProxyUserProcessor();
processor.RegUser(user);

对比一下装饰器模式和代理模式: 前者是传递了一个UserProcessor对象到构造函数里面去,而后者是内置实例化了一个对象。这两者的侧重点不同,装饰器可以多层装饰,处理的场景不同。
这两者都是静态实现,说白了都是需要去添加代理或者装饰器类的。

使用.Net Remoting/RealProxy 实现动态代理

局限在业务类必须是继承自MarshalByRefObject类型,只有一个父类,必须是这个MarshalByRefObject。

public class UserProcessor : MarshalByRefObject, IUserProcessor
{
  public void RegUser(User user)
  {
    Console.WriteLine("用户已注册。用户名称{0} Password{1}", user.Name, user.Password);
  }
}

同时还需要两个代理类,分别是真实代理和透明代理:
透明代理类的代码是固定的,不需要修改:

//透明代理
public static class TransparentProxy
{
  public static T Create<T>()
  {
    T instance = Activator.CreateInstance<T>();
    MyRealProxy<T> realProxy = new MyRealProxy<T>(instance);
    T transparentProxy = (T)realProxy.GetTransparentProxy();
    return transparentProxy;
  }
}

下面是真实代理,需要自己去重新实现的:

public class MyRealProxy<T> : RealProxy
{
  private T tTarget;
  public MyRealProxy(T target)
      : base(typeof(T))
  {
    this.tTarget = target;
  }

  public override IMessage Invoke(IMessage msg)
  {
    BeforeProceede(msg);//Log  try-catch
    IMethodCallMessage callMessage = (IMethodCallMessage)msg;
    object returnValue = callMessage.MethodBase.Invoke(this.tTarget, callMessage.Args);
    AfterProceede(msg);
    return new ReturnMessage(returnValue, new object[0], 0, null, callMessage);
  }

  public void BeforeProceede(IMessage msg)
  {
    Console.WriteLine("方法执行前可以加入的逻辑");
  }
  public void AfterProceede(IMessage msg)
  {
    Console.WriteLine("方法执行后可以加入的逻辑");
  }
}

在真实使用的时候,实际上会调用Invoke方法,如下:

User user = new User()
{
  Name = "Richard",
  Password = "123456"
};

UserProcessor processor = new UserProcessor();
processor.RegUser(user);

Console.WriteLine("*********************");
UserProcessor userProcessor = TransparentProxy.Create();
userProcessor.RegUser(user);

使用Castle\DynamicProxy 实现动态代理

方法必须是虚方法:

public class UserProcessor : IUserProcessor
{
  // 必须带上virtual 否则无效~
  public virtual void RegUser(User user) 
  {
    Console.WriteLine($"用户已注册。Name:{user.Name},PassWord:{user.Password}");
  }
}

需要在Nuget里面添加这个dll:using Castle.DynamicProxy;//Castle.Core

public class MyInterceptor : IInterceptor
{
  public void Intercept(IInvocation invocation)
  {
    PreProceed(invocation);
    invocation.Proceed();//就是调用原始业务方法
    PostProceed(invocation);
  }
  public void PreProceed(IInvocation invocation)
  {
    Console.WriteLine("方法执行前");
  }

  public void PostProceed(IInvocation invocation)
  {
    Console.WriteLine("方法执行后");
  }
}

使用的时候可以这么用,Castle的核心原理是基于Emit实现的:

User user = new User()
{
  Name = "Richard",
  Password = "123456"
};
ProxyGenerator generator = new ProxyGenerator();
MyInterceptor interceptor = new MyInterceptor();
UserProcessor userprocessor = generator.CreateClassProxy<UserProcessor>(interceptor);
userprocessor.RegUser(user);

静态织入实现AOP

PostSharp,收费工具,扩展编译工具,生成加入额外代码

Unity容器

首先,需要引入Unity, Unity.Abstractions, Unity.Configuration, Unity.Container, Unity.Interception, Unity,Interception.Configuration这些关于Unity的Nuget包。
然后,还需要准备一个配置文件如下:

<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/>
    <!--Microsoft.Practices.Unity.Configuration.UnityConfigurationSection-->
  </configSections>
  <unity>
    <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/>
    <containers>
      <container name="aopContainer">
        <extension type="Interception"/>  
        <register type="MyAOP.UnityWay.IUserProcessor,MyAOP" mapTo="MyAOP.UnityWay.UserProcessor,MyAOP">
          <interceptor type="InterfaceInterceptor"/> 
          <interceptionBehavior type="MyAOP.UnityWay.MoniterBehavior, MyAOP"/>
          <interceptionBehavior type="MyAOP.UnityWay.LogBeforeBehavior, MyAOP"/>
          <interceptionBehavior type="MyAOP.UnityWay.ParameterCheckBehavior, MyAOP"/>
          <interceptionBehavior type="MyAOP.UnityWay.CachingBehavior, MyAOP"/>
          <interceptionBehavior type="MyAOP.UnityWay.ExceptionLoggingBehavior, MyAOP"/>
          <interceptionBehavior type="MyAOP.UnityWay.LogAfterBehavior, MyAOP"/> 
        </register>
      </container>
    </containers>
  </unity>
</configuration>

这里面定义了容器的类型type,还有容器的 名字name,这是是aopContainer。其中的核心是register, 完整类型名称+dll名称: type="MyAOP.UnityWay.IUserProcessor,MyAOP" mapTo="MyAOP.UnityWay.UserProcessor,MyAOP",MyAOP是dll的名称,把接口IUserProcessor映射到UserProcessor这个上面去,MyAOP是完整名称。后面通过容器创建实例的时候就是依靠这一段配置实现的。
interceptor type="InterfaceInterceptor"是配置接口的扩展,还可以配置类和方法。
j接着,在具体使用的时候是这样,在调用RegUser方法的时候,会依次去执行配置文件里面的这些behavior,例如MoniterBehavior,LogBeforeBehavior,ParameterCheckBehavior,CachingBehavior,ExceptionLoggingBehavior,LogAfterBehavior。

User user = new User()
{
  Name = "Richard",
  Password = "1234567890123456789"
};
//配置UnityContainer
IUnityContainer container = new UnityContainer();
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config");
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);

UnityConfigurationSection configSection = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName);
configSection.Configure(container, "aopContainer");

IUserProcessor processor = container.Resolve<IUserProcessor>();
processor.RegUser(user);
processor.GetUser(user);

最后,我们需要看一下,这些Behavior到底是怎么实现的呢?
必须实现接口IInterceptionBehavior,在方法Invoke中扩展逻辑:

  • 在执行注册方法之前执行监控方法:
public class MoniterBehavior : IInterceptionBehavior
{
  public IEnumerable<Type> GetRequiredInterfaces()
  {
    return Type.EmptyTypes;
  }

  public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
  {
     Console.WriteLine(this.GetType().Name);
     string methodName = input.MethodBase.Name;
     Stopwatch stopwatch = new Stopwatch();
     stopwatch.Start(); 
     // 可以在这儿添加方法之前的逻辑

     var query = getNext().Invoke(input, getNext);//后续逻辑执行
     stopwatch.Stop();
     Console.WriteLine($"{this.GetType().Name}执行耗时:{stopwatch.ElapsedMilliseconds} ms"); 
     return query; 
  }

  public bool WillExecute
  {
    get { return true; }
  }
}
  • 在执行注册方法之前执行日志方法:
public class LogBeforeBehavior : IInterceptionBehavior
{
  public IEnumerable<Type> GetRequiredInterfaces()
  {
    return Type.EmptyTypes;
  }

  public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
  {
    Console.WriteLine("LogBeforeBehavior");
    foreach (var item in input.Inputs)
    {
      Console.WriteLine(item.ToString());//反射获取更多信息
    }
    var query = getNext().Invoke(input, getNext);

    //下一个节点的方法已经执行完毕   
    //在这里计入方法执行后的逻辑
    return query;
  }

  public bool WillExecute
  {
    get { return true; }
  }
}
  • 在执行注册方法之前执行检查参数behavior
public class ParameterCheckBehavior : IInterceptionBehavior
{
  public IEnumerable<Type> GetRequiredInterfaces()
  {
    return Type.EmptyTypes;
  }

  public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
  {
    Console.WriteLine("ParameterCheckBehavior");
    //通过特性校验
    User user = input.Inputs[0] as User;
    if (user.Password.Length < 10)
    {
      //throw new Exception(); 
      return input.CreateExceptionMethodReturn(new Exception("密码长度不能小于10位"));
    }
    else
    {
      Console.WriteLine("参数检测无误");
      return getNext().Invoke(input, getNext);
    }
  }

  public bool WillExecute
  {
    get { return true; }
  }
}
  • 在执行注册方法之前执行缓存方法
public class CachingBehavior : IInterceptionBehavior
{
  public IEnumerable<Type> GetRequiredInterfaces()
  {
    return Type.EmptyTypes;
  }

  public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
  {
    Console.WriteLine("CachingBehavior");
    //input.Target.GetType().GetCustomAttributes()
    if (input.MethodBase.Name.Equals("GetUser"))
    return input.CreateMethodReturn(new User() { Id = 234, Name = "Richard" });
    return getNext().Invoke(input, getNext);
  }

  public bool WillExecute
  {
    get { return true; }
  }
}
  • 让异常包裹所有的方法,先执行业务逻辑
public class ExceptionLoggingBehavior : IInterceptionBehavior
{
  public IEnumerable<Type> GetRequiredInterfaces()
  {
    return Type.EmptyTypes;
  }

  public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
  {
    Console.WriteLine("ExceptionLoggingBehavior");
    IMethodReturn methodReturn = getNext()(input, getNext);
    if (methodReturn.Exception == null)
    {
      Console.WriteLine("无异常");
    }
    else
    {
      Console.WriteLine($"异常:{methodReturn.Exception.Message}");
    }
    return methodReturn;
  }

  public bool WillExecute
  {
    get { return true; }
  }
}
  • 执行LogAfter方法,也是需要等业务方法执行完之后再执行的。
public class LogAfterBehavior : IInterceptionBehavior
{
  public IEnumerable<Type> GetRequiredInterfaces()
  {
    return Type.EmptyTypes;
  }

  public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
  {
    Console.WriteLine("LogAfterBehavior");
    foreach (var item in input.Inputs)
    {
      Console.WriteLine(item.ToString());//反射获取更多信息
    }
    IMethodReturn methodReturn = getNext()(input, getNext);
    Console.WriteLine("LogAfterBehavior" + methodReturn.ReturnValue);
    return methodReturn;
  }

  public bool WillExecute
  {
    get { return true; }
  }
}

回过头再看一下整个的执行过程:一开始进来的时候是MoniterBehavior方法,开启秒表,然后依次往后执行LogBeforeBehavior,ParameterCheckBehavior,CachingBehavior,ExceptionLoggingBehavior,LogAfterBehavio,遇到getNext()(input, getNext)就往后执行,直到执行到业务代码完成了之后,再依次往前返回,实际上就是net core里面的管道。
getNext()(input, getNext)getNext().Invoke(input, getNext)是一个意思,getNext()返回是一个委托,可以直接invoke进行执行,也可以直接调用。

值得注意的问题:

  1. 顺序问题,配置文件的注册顺序是调用顺序,然后才是业务方法,但是扩展逻辑可以在业务方法后;例如Exception那里。
  2. 接口方法不需要某个AOP扩展:a.判断方法; b.推荐用特性(在接口声明的地方用)

Input.Target.GetType().GetCustomAttributes()获取特性,然后可以针对某些方法不生效,例如:

//接口声明特性
public interface IUserProcessor
{
  [TestAttritubte]
  void RegUser(User user);
}

文章作者: Chaoqiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chaoqiang !
评论
 上一篇
DotNet-Advanced-Series-2-3-CLRCore DotNet-Advanced-Series-2-3-CLRCore
document.querySelectorAll('.github-emoji') .forEach(el => { if (!el.dataset.src) { return
下一篇 
C# Advanced Tutorial 1-15-AwaitAsync C# Advanced Tutorial 1-15-AwaitAsync
主要内容概要1 await/async语法和使用2 原理探究和使用建议 Await Asyncawait/async 是C# 的保留关键字 ,.Net framework4.5 版本出现的,通常是成对出现。async修饰方法,可以单独出现
  目录