想象一下,当程序所有的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即将来临。你的Boss告诉你,虽然程序没问题,但某些方法为什么执行这么慢,性能堪忧。领会了Boss的意图之后,漫长的排查问题开始了。你会写日志,或者是其他工具来追踪原因。那么如何以一种优雅的形式,并且不侵入业务代码的形式来跟踪呢?这正是本文的内容。
通过观察,你发现方法
执行缓慢,可能有性能问题,因为这是一个线上的版本,你无法进行Debug,所以你通过日志的形式来追踪执行步骤:
- Do
- class Foo1
- {
- void Do()
- {
- //日志记录开始
- //性能监控开始
- DoSomething();
- //日志记录结束
- //性能监控结束
- }
- }
看起来不错,解决问题之后,测试又发现另一个方法
貌似也有问题,然后你一样画葫芦,虽然麻烦了一点,但总归是有解决方案:
- Handle
- class Foo2
- {
- void Handle()
- {
- //日志记录开始
- //性能监控开始
- DoSomething();
- //日志记录结束
- //性能监控结束
- }
- }
上述两段代码,虽然看起来很丑,但毕竟是能解决问题,但是代码的风格不怎么让人舒服:
知道了问题之后,第二种风格的代码出现了:
- class Bar
- {
- void Do()
- {
- common.BeginLog();
- common.BeginWatch();
- common.BeginTransaction();
- foo.Do();
- common.Commit();
- common.EndWatch();
- common.EndLog();
- }
- }
看似是个不错的方案,但实际上还是没解决本质问题。虽然将日志,监控放到了
中,但每个方法还是要写这一大堆和业务无关的代码,这压根什么也没解决,这个方法的层次结构如下图所示:
- Common
什么是AOP
如果你是第一次接触『面向切面编程』,可能这些概念太过复杂和笼统,我建议先翻阅相关书籍、博客,最好对AOP有一定的了解。
什么是『切面』?
『面向切面编程』总共6个字,想必最难理解的还是『切面』两字吧。
所以『切面』是一种横向的拦截,而非纵向继承的机制。
使用纵向继承的方式来拦截方法:
- class Order
- {
- public virtual void Add()
- {
- Console.WriteLine("新增订单");
- }
- }
- class OrderExt : Order
- {
- public override void Add()
- {
- //开启事务
- BeginTransaction();
- base.Add();
- //提交事务
- Commit();
- }
- void BeginTransaction() { }
- void Commit() { }
- }
缺点上面已经提到过了,不再重复。
使用AOP横向拦截方法,通过动态代理类来实现:
- class Order
- {
- public virtual void Add()
- {
- Console.WriteLine("新增订单");
- }
- }
- class TransactionUtility
- {
- public
- void
- BeginTransaction
- (
- )
- { }
- public
- void
- Commit
- (
- )
- { }
- }
- class OrderProxy
- {
- public Order o;
- public TransactionUtility u;
- public void Add()
- {
- u.BeginTransaction();
- o.Add();
- u.Commit();
- }
- }
当然这个
一般是通过框架(比如Spring)在运行时动态创建的,所以叫动态代理对象。客户端不知道真正调用的对象其实是
- OrderProxy
。整个过程如下所示:
- OrderProxy
1.) Target:目标类,需要被代理的类
2.) Join Point:连接点,指那些可能被拦截到的方法。
3.) Point Cut:切入点,已经被增强的连接点
4.) 切面类:提供了事务管理,日志记录,性能监控等公共操作的类
5.) Advice:通知点,用来增强代码
6.) Weaving:织入,将Advice应用到目标对象Target,是创建新的代理对象Proxy的过程。
7.) Proxy:代理类
8.) Aspect:切面,是切入点PointCut和通知Advice的结合,2点确定一条线,多条线组合成面
很遗憾,在Unity中没有好的AOP框架,虽然.NET有很多AOP框架,但考虑到Unity的跨平台,很多技术并不兼容。所以我以另一种形式间接的实现了AOP。
理解了AOP之后,实际上我只关注两点:
定义一个
类,指定需要被代理的类的方法,以及拦截策略:
- Proxy
- public class Proxy
- {
- public static Proxy Instance = new Proxy();
- private IInvocationHandler _invocationHandler;
- private object _target;
- private string _method;
- private object[] _args;
- private Proxy()
- {
- }
- public Proxy SetInvocationHandler(IInvocationHandler invocationHandler)
- {
- _invocationHandler = invocationHandler;
- return this;
- }
- public Proxy SetTarget(object target)
- {
- _target = target;
- return this;
- }
- public Proxy SetMethod(string method)
- {
- _method = method;
- return this;
- }
- public Proxy SetArgs(object[] args)
- {
- _args = args;
- return this;
- }
- public object Invoke()
- {
- var methodInfo = _target.GetType().GetMethod(_method);
- return _invocationHandler.Invoke(_target, methodInfo, _args);
- }
- }
拦截策略是个公共接口,提供策略:
- public interface IInvocationHandler
- {
- void PreProcess();
- object
- Invoke
- (
- object proxy, MethodInfo method, object[] args
- )
- ;
- void PostProcess();
- }
实现接口,就可以自定义拦截方法,一个日志的策略如下所示:
- public class LogInvocationHandler:IInvocationHandler
- {
- public void PreProcess()
- {
- LogFactory.Instance.Resolve<ConsoleLogStrategy>().Log("Pre Process");
- }
- public object Invoke(object target, MethodInfo method, object[] args)
- {
- PreProcess();
- var result= method.Invoke(target, args);
- PostProcess();
- return result;
- }
- public void PostProcess()
- {
- LogFactory.Instance.Resolve<ConsoleLogStrategy>().Log("Post Process");
- }
- }
假设你需要对
对象的
- repository
方法进行拦截,即执行前后进行日志的打印,你可以这样来使用:
- Test
- Proxy.Instance.SetTarget(repository)
- .SetMethod("Test")
- .SetArgs(new object[] {})
- .SetInvocationHandler(new LogInvocationHandler())
- .Invoke();
AOP思想是非常重要的重构手段,以不侵入的形式解耦业务逻辑和拦截方法。本质上是以横向扩展的形式替换了传统的纵向继承方式来实现。遗憾的是,在Unity中并没有好的AOP框架,我按照AOP的思想,简化了实现模式,以曲线的形式实现对方法的拦截。
源代码托管在Github上,点击此了解
欢迎关注我的公众号:
来源: https://juejin.im/post/59f5b3a0518825299a467471