想象一下,当程序所有的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即将来临。你的Boss告诉你,虽然程序没问题,但某些方法为什么执行这么慢,性能堪忧。领会了Boss的意图之后,漫长的排查问题开始了。你会写日志,或者是其他工具来追踪原因。那么如何以一种优雅的形式,并且不侵入业务代码的形式来跟踪呢?这正是本文的内容。

跟踪问题

通过观察,你发现方法Do执行缓慢,可能有性能问题,因为这是一个线上的版本,你无法进行Debug,所以你通过日志的形式来追踪执行步骤:

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?

  • 名称来源->『Aspect Oriented Programming』的缩写,中文翻译即『面向切面编程』
  • 应用场景->为日志记录,性能监控,安全控制,事务处理,异常处理等与具体业务逻辑无关,却又需要在全局范围执行的功能提供了一种良好的重用并且和业务逻辑解耦
  • 核心理念->AOP的思想是围绕着切面进行的,所谓的『切面』就是对目标对象的某种操作进行拦截,在系统其他部分调用目标对象的某种操作时拦截这些调用,并在进行真正的调用前、后执行一段中间逻辑
  • 实现方式->AOP的实现方式被分为『静态织入』和『动态织入』。采用『静态织入』方式通过扩展编译器对代码的中间语言IL插入代码,从而对目标对象进行拦截。『动态织入』则在运行时创建动态代理对象来拦截

如果你是第一次接触『面向切面编程』,可能这些概念太过复杂和笼统,我建议先翻阅相关书籍、博客,最好对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();
}
}

当然这个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思想

很遗憾,在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上,点击此了解

Unity应用架构设计(12)——AOP思想的实践的更多相关文章

  1. Unity应用架构设计(11)——一个网络层的构建

    对于客户端应用程序,免不了和远程服务打交道.设计一个良好的『服务层』能帮我们规范和分离业务代码,提高生产效率.服务层最核心的模块一定是怎样发送请求,虽然Mono提供了很多C#网络请求类,诸如WebCl ...

  2. Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 2)

    MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...

  3. Unity应用架构设计(4)——设计可复用的SubView和SubViewModel(Part 1)

    『可复用』这个词相信大家都熟悉,通过『可复用』的组件,可以大大提高软件开发效率. 值得注意的事,当我们设计一个可复用的面向对象组件时,需要保证其独立性,也就是我们熟知的『高内聚,低耦合』原则. 组件化 ...

  4. Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 1)

    初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...

  5. Unity应用架构设计(13)——日志组件的实施

    对于应用程序而言,日志是非常重要的功能,通过日志,我们可以跟踪应用程序的数据状态,记录Crash的日志可以帮助我们分析应用程序崩溃的原因,我们甚至可以通过日志来进行性能的监控.总之,日志的好处很多,特 ...

  6. Unity应用架构设计(9)——构建统一的 Repository

    谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化.Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化 ...

  7. Unity应用架构设计(7)——IoC工厂理念先行

    一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂.IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常卓越的的控制反转.依赖注入框架.遗憾的是, ...

  8. Unity应用架构设计(6)——设计动态数据集合ObservableList

    什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...

  9. Unity应用架构设计(2)——使用中介者模式解耦ViewModel之间通信

    当你开发一个客户端应用程序的时候,往往一个单页会包含很多子模块,在不同的平台下,这些子模块又被叫成子View(视图),或者子Component(组件).越是复杂的页面,被切割出来的子模块就越多,子模块 ...

随机推荐

  1. Windows8.1 + Nvidia cuda8.0 + Vs2015

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Vs2015 1.查看本机配置,查看显卡类型是否支持NVIDIA GPU选中计算机-->右键属性-->设备管理 ...

  2. 分辨率、像素和PPI

    屏幕尺寸是指屏幕对角线的长度,一般以英寸为单位,1英寸(inch)=2.54厘米(cm).传统意义上的照片尺寸也是这个概念.所以同样尺寸(指对角线)的屏幕,也可能长宽比率不同.像素(Pixel):是位 ...

  3. 001---Hibernate简介( 开源O/R映射框架)

    该系列教程是使用hibernate3,hibernate4和3有区别(集成spring,使用等),请注意 001---Hibernate简介(开源O/R映射框架) ORM(Object Relatio ...

  4. 消息:SQL Server 2017(vNext)的第三个公开的CTP(社区技术预览版)发布了

    今天看到了一个新闻,跟大家分享一下,有兴趣的可以去尝试一下. SQL Server 2017 CTP3于5月23日发布了,详细版本号是6.7.55.0. 大家可以去安装试试.在下载页面,目前是SQL  ...

  5. Predix Asset Service深度分析

    前言 在IIOT领域,面临着保存海量数据的挑战,具体到Asset层面,则要保存物理对象,逻辑对象,复杂的关系,并支持对象间的组合,分类,标签和高效查询.总结来说,可以归纳为如下几种需求:   灵活的建 ...

  6. JavaSE教程-03Java中分支语句与四种进制转换-练习

    练习1: 老师如果带100或以上则请客吃饭 import java.util.Scanner; public class Test2{ public static void main(String[] ...

  7. 深入探索C++对象模型(五)

    构造.解构.拷贝语意学(Semantics of Construction,Destruction, and Copy) 一般而言,class的data member应该被初始化,并且只在constr ...

  8. pb传输优化浅谈

    在正式切入今天要谈的优化之前,先碎碎念一些自己过去这几年的经历.很久没有登录过博客园了,今天也是偶然兴起打开上来看一下,翻看了下自己的随笔,最后一篇原创文章发布时间是2015年的4月,今天是2017年 ...

  9. MACOS关闭指定端口

    因为用IDEA写项目的时候,有的时候结束Jetty导致端口没有释放,所以会出现占用的情况. MacOS结束端口占用进程的命令,和Linux的一样.先执行如下命令: lsof -i:8080 会有类似下 ...

  10. PHP的SQL注入技术实现以及预防措施

    SQL 攻击(SQL injection,台湾称作SQL资料隐码攻击),简称注入攻击,是发生于应用程序之数据库层的安全漏洞.简而言之,是在输入的字符串之中注入SQL指 令,在设计不良的程序当中忽略了检 ...