AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的中统一处理业务逻辑的一种技术,比较常见的场景是:日志记录,错误捕获、性能监控等
AOP的本质是通过代理对象来间接执行真实对象,在代理类中往往会添加装饰一些额外的业务代码,比如如下代码:
class RealA { public virtual string Pro { get; set; } public virtual void ShowHello(string name) { Console.WriteLine($"Hello!{name},Welcome!"); } } //调用: var a = new RealA(); a.Pro = "测试"; a.ShowHello("梦在旅途"); |
这段代码很简单,只是NEW一个对象,然后设置属性及调用方法,但如果我想在设置属性前后及调用方法前后或报错都能收集日志信息,该如何做呢?可能大家会想到,在设置属性及调用方法前后都加上记录日志的代码不就可以了,虽然这样是可以,但如果很多地方都要用到这个类的时候,那重复的代码是否太多了一些吧,所以我们应该使用代理模式或装饰模式,将原有的真实类RealA委托给代理类ProxyRealA来执行,代理类中在设置属性及调用方法时,再添加记录日志的代码就可以了,这样可以保证代码的干净整洁,也便于代码的后期维护。(注意,在C#中若需被子类重写,父类必需是虚方法或虚属性virtual)
如下代码:
class ProxyRealA : RealA { public override string Pro { get { return base.Pro; } set { ShowLog("设置Pro属性前日志信息"); base.Pro = value; ShowLog($"设置Pro属性后日志信息:{value}"); } } public override void ShowHello(string name) { try { ShowLog("ShowHello执行前日志信息"); base.ShowHello(name); ShowLog("ShowHello执行后日志信息"); } catch(Exception ex) { ShowLog($"ShowHello执行出错日志信息:{ex.Message}"); } } private void ShowLog(string log) { Console.WriteLine($"{DateTime.Now.ToString()}-{log}"); } } //调用: var aa = new ProxyRealA(); aa.Pro = "测试2"; aa.ShowHello("zuowenjun.cn"); |
这段代码同样很简单,就是ProxyRealA继承自RealA类,即可看成是ProxyRealA代理RealA,由ProxyRealA提供各种属性及方法调用。这样在ProxyRealA类内部属性及方法执行前后都有统一记录日志的代码,不论在哪里用这个RealA类,都可以直接用ProxyRealA类代替,因为里氏替换原则,父类可以被子类替换,而且后续若想更改日志记录代码方式,只需要在ProxyRealA中更改就行了,这样所有用到的ProxyRealA类的日志都会改变,是不是很爽。上述执行结果如下图示:
以上通过定义代理类的方式能够实现在方法中统一进行各种执行点的拦截代码逻辑处理,拦截点(或者称为:横切面,切面点)一般主要为:执行前,执行后,发生错误,虽然解决了之前直接调用真实类RealA时,需要重复增加各种逻辑代码的问题,但随之而来的新问题又来了,那就是当一个系统中的类非常多的时候,如果我们针对每个类都定义一个代理类,那么系统的类的个数会成倍增加,而且不同的代理类中可能某些拦截业务逻辑代码都是相同的,这种情况同样是不能允许的,那有没有什么好的办法呢?答案是肯定的,以下是我结合网上资源及个人总结的如下几种常见的实现AOP的方式,各位可以参考学习。
第一种:静态织入,即:在编译时,就将各种涉及AOP拦截的代码注入到符合一定规则的类中,编译后的代码与我们直接在RealA调用属性或方法前后增加代码是相同的,只是这个工作交由编译器来完成。
PostSharp:PostSharp的Aspect是使用Attribute实现的,我们只需事先通过继承自OnMethodBoundaryAspect,然后重写几个常见的方法即可,如:OnEntry,OnExit等,最后只需要在需要进行AOP拦截的属性或方法上加上AOP拦截特性类即可。由于PostSharp是静态织入的,所以相比其它的通过反射或EMIT反射来说效率是最高的,但PostSharp是收费版本的,而且网上的教程比较多,我就不在此重复说明了,大家可以参见:使用PostSharp在.NET平台上实现AOP
第二种:EMIT反射,即:通过Emit反射动态生成代理类,如下Castle.DynamicProxy的AOP实现方式,代码也还是比较简单的,效率相对第一种要慢一点,但对于普通的反射来说又高一些,代码实现如下:
using Castle.Core.Interceptor; using Castle.DynamicProxy; using NLog; using NLog.Config; using NLog.Win32.Targets; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp { class Program { static void Main(string[] args) { ProxyGenerator generator = new ProxyGenerator(); var test = generator.CreateClassProxy<TestA>(new TestInterceptor()); Console.WriteLine($"GetResult:{test.GetResult(Console.ReadLine())}"); test.GetResult2("test"); Console.ReadKey(); } } public class TestInterceptor : StandardInterceptor { private static NLog.Logger logger; protected override void PreProceed(IInvocation invocation) { Console.WriteLine(invocation.Method.Name + "执行前,入参:" + string.Join(",", invocation.Arguments)); } protected override void PerformProceed(IInvocation invocation) { Console.WriteLine(invocation.Method.Name + "执行中"); try { base.PerformProceed(invocation); } catch (Exception ex) { HandleException(ex); } } protected override void PostProceed(IInvocation invocation) { Console.WriteLine(invocation.Method.Name + "执行后,返回值:" + invocation.ReturnValue); } private void HandleException(Exception ex) { if (logger == null) { LoggingConfiguration config = new LoggingConfiguration(); ColoredConsoleTarget consoleTarget = new ColoredConsoleTarget(); consoleTarget.Layout = "${date:format=HH\\:MM\\:ss} ${logger} ${message}"; config.AddTarget("console", consoleTarget); LoggingRule rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget); config.LoggingRules.Add(rule1); LogManager.Configuration = config; logger = LogManager.GetCurrentClassLogger(); //new NLog.LogFactory().GetCurrentClassLogger(); } logger.ErrorException("error",ex); } } public class TestA { public virtual string GetResult(string msg) { string str = $"{DateTime.Now.ToString("yyyy-mm-dd HH:mm:ss")}---{msg}"; return str; } public virtual string GetResult2(string msg) { throw new Exception("throw Exception!"); } } } |
简要说明一下代码原理,先创建ProxyGenerator类实例,从名字就看得出来,是代理类生成器,然后实例化一个基于继承自StandardInterceptor的TestInterceptor,这个TestInterceptor是一个自定义的拦截器,最后通过generator.CreateClassProxy<TestA>(new TestInterceptor())动态创建了一个继承自TestA的动态代理类,这个代理类只有在运行时才会生成的,后面就可以如代码所示,直接用动态代理类对象实例Test操作TestA的所有属性与方法,当然这里需要注意,若需要被动态代理类所代理并拦截,则父类的属性或方法必需是virtual,这点与我上面说的直接写一个代理类相同。
上述代码运行效果如下:
第三种:普通反射+利用Remoting的远程访问对象时的直实代理类来实现,代码如下,这个可能相比以上两种稍微复杂一点:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Remoting.Activation; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Proxies; using System.Text; using System.Threading.Tasks; namespace ConsoleApp { class Program { static void Main(string[] args) { var A = new AopClass(); A.Hello(); var aop = new AopClassSub("梦在旅途"); aop.Pro = "test"; aop.Output("hlf"); aop.ShowMsg(); Console.ReadKey(); } } [AopAttribute] public class AopClass : ContextBoundObject { public string Hello() { return "welcome"; } } public class AopClassSub : AopClass { public string Pro = null; private string Msg = null; public AopClassSub(string msg) { Msg = msg; } public void Output(string name) { Console.WriteLine(name + ",你好!-->P:" + Pro); } public void ShowMsg() { Console.WriteLine($"构造函数传的Msg参数内容是:{Msg}"); } } public class AopAttribute : ProxyAttribute { public override MarshalByRefObject CreateInstance(Type serverType) { AopProxy realProxy = new AopProxy(serverType); return realProxy.GetTransparentProxy() as MarshalByRefObject; } } public class AopProxy : RealProxy { public AopProxy(Type serverType) : base(serverType) { } public override IMessage Invoke(IMessage msg) { if (msg is IConstructionCallMessage) { IConstructionCallMessage constructCallMsg = msg as IConstructionCallMessage; IConstructionReturnMessage constructionReturnMessage = this.InitializeServerObject((IConstructionCallMessage)msg); RealProxy.SetStubData(this, constructionReturnMessage.ReturnValue); Console.WriteLine("Call constructor"); return constructionReturnMessage; } else { IMethodCallMessage callMsg = msg as IMethodCallMessage; IMessage message; try { Console.WriteLine(callMsg.MethodName + "执行前。。。"); object[] args = callMsg.Args; object o = callMsg.MethodBase.Invoke(GetUnwrappedServer(), args); Console.WriteLine(callMsg.MethodName + "执行后。。。"); message = new ReturnMessage(o, args, args.Length, callMsg.LogicalCallContext, callMsg); } catch (Exception e) { message = new ReturnMessage(e, callMsg); } Console.WriteLine(message.Properties["__Return"]); return message; } } } } |
以上代码实现步骤说明:
1.这里定义的一个真实类AopClass必需继承自ContextBoundObject类,而ContextBoundObject类又直接继承自MarshalByRefObject类,表明该类是上下文绑定对象,允许在支持远程处理的应用程序中跨应用程序域边界访问对象,说白了就是可以获取这个真实类的所有信息,以便可以被生成动态代理。
2.定义继承自ProxyAttribute的代理特性标识类AopAttribute,以表明哪些类可以被代理,同时注意重写CreateInstance方法,在CreateInstance方法里实现通过委托与生成透明代理类的过程,realProxy.GetTransparentProxy() 非常重要,目的就是根据定义的AopProxy代理类获取生成透明代理类对象实例。
3.实现通用的AopProxy代理类,代理类必需继承自RealProxy类,在这个代理类里面重写Invoke方法,该方法是统一执行被代理的真实类的所有方法、属性、字段的出入口,我们只需要在该方法中根据传入的IMessage进行判断并实现相应的拦截代码即可。
4.最后在需要进行Aop拦截的类上标注AopAttribute即可(注意:被标识的类必需是如第1条说明的继承自ContextBoundObject类),在实际调用的过程中是感知不到任何的变化。且AopAttribute可以被子类继承,也就意味着所有子类都可以被代理并拦截。
如上代码运行效果如下: