想象一下,当程序所有的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即将来临。你的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. Python爬虫学习之获取网页源码

    偶然的机会,在知乎上看到一个有关爬虫的话题<利用爬虫技术能做到哪些很酷很有趣很有用的事情?>,因为强烈的好奇心和觉得会写爬虫是一件高大上的事情,所以就对爬虫产生了兴趣. 关于网络爬虫的定义 ...

  2. Apache solr(一)

    概念:Apache Solr 是一个开源的搜索服务器.Solr 使用 Java 语言开发,主要基于 HTTP 和 Apache Lucene 实现.Apache Solr 中存储的资源是以 Docum ...

  3. 作为.net程序员学jsp,伤不起

    <%@page import="java.sql.*"%> <%@ page language="java" import="jav ...

  4. ASP实现计算机爱好者网站,可以直接浏览

    利用ASP制作的计算机爱好者协会网站,内容比较全面,具有母版和子页的功能,利用ACCESS数据库进行资源存储.适合新手学习和网页制作比赛参考 下载地址:http://download.csdn.net ...

  5. 第 5 章 MySQL 备份与恢复

    第 5 章 MySQL 备份与恢复 前言 数据库的备份与恢复一直都是 DBA 工作中最为重要的部分之一,也是基本工作之一.任何正式环境的数据库都必须有完整的备份计划和恢复测试,本章内容将主要介绍 My ...

  6. eclipse内存溢出设置

    1. arguments中的内容添加红色部分:   -Dcatalina.base="E:\workspace\.metadata\.plugins   \org.eclipse.wst.s ...

  7. JQuery操作表单控件

    1.jquery操作radio <html> <head>   <script type="text/javascript" src="jq ...

  8. JMeter-MyEclipse编译运行问题(Could not read JMeter properties file)

    JMeter-MyEclipse编译运行问题按照 此贴 http://phoenix0529.iteye.com/blog/1530728 进行配置,然后用Ant编译Build.xml 是可以的. 但 ...

  9. js文件下载及命名(兼容多浏览器)

    函数功能:实现主流浏览器的文件下载功能: 兼容性: >=IE10,Edge,chrome,firefox; 与后台的请求方式:GET请求,  url携带参数  url?id=123(隐藏文件真实 ...

  10. 一天搞定CSS: CSS选择器优先级--08

    选择器优先级:是指代码的执行顺序 通俗的说,就是多个选择器同时对一个标签分别添加样式,那么这个标签显示那个选择器设置的样式 1.优先级规则 2.规则1和2说明 优先级相同,谁后谁优先 优先级不同,优先 ...