想象一下,当程序全部的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即未来临。你的Boss告诉你,虽然程序没问题,但某些方法为何执行这么慢,性能堪忧。领会了Boss的意图以后,漫长的排查问题开始了。你会写日志,或者是其余工具来追踪缘由。那么如何以一种优雅的形式,而且不侵入业务代码的形式来跟踪呢?这正是本文的内容。git
经过观察,你发现方法Do
执行缓慢,可能有性能问题,由于这是一个线上的版本,你没法进行Debug,因此你经过日志的形式来追踪执行步骤:github
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
中,但每一个方法仍是要写这一大堆和业务无关的代码,这压根什么也没解决,这个方法的层次结构以下图所示:ide
什么是AOP?工具
若是你是第一次接触『面向切面编程』,可能这些概念太过复杂和笼统,我建议先翻阅相关书籍、博客,最好对AOP有必定的了解。性能
什么是『切面』?测试
『面向切面编程』总共6个字,想必最难理解的仍是『切面』两字吧。this
因此『切面』是一种横向的拦截,而非纵向继承的机制。
使用纵向继承的方式来拦截方法:
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();
}
}复制代码
固然这个OrderProxy
通常是经过框架(好比Spring)在运行时动态建立的,因此叫动态代理对象。客户端不知道真正调用的对象实际上是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上,点击此了解
欢迎关注个人公众号: