Unity应用架构设计(12)——AOP思想的实践
想象一下,当程序所有的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即将来临。你的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思想的实践的更多相关文章
- Unity应用架构设计(11)——一个网络层的构建
对于客户端应用程序,免不了和远程服务打交道.设计一个良好的『服务层』能帮我们规范和分离业务代码,提高生产效率.服务层最核心的模块一定是怎样发送请求,虽然Mono提供了很多C#网络请求类,诸如WebCl ...
- Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 2)
MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...
- Unity应用架构设计(4)——设计可复用的SubView和SubViewModel(Part 1)
『可复用』这个词相信大家都熟悉,通过『可复用』的组件,可以大大提高软件开发效率. 值得注意的事,当我们设计一个可复用的面向对象组件时,需要保证其独立性,也就是我们熟知的『高内聚,低耦合』原则. 组件化 ...
- Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 1)
初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...
- Unity应用架构设计(13)——日志组件的实施
对于应用程序而言,日志是非常重要的功能,通过日志,我们可以跟踪应用程序的数据状态,记录Crash的日志可以帮助我们分析应用程序崩溃的原因,我们甚至可以通过日志来进行性能的监控.总之,日志的好处很多,特 ...
- Unity应用架构设计(9)——构建统一的 Repository
谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化.Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化 ...
- Unity应用架构设计(7)——IoC工厂理念先行
一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂.IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常卓越的的控制反转.依赖注入框架.遗憾的是, ...
- Unity应用架构设计(6)——设计动态数据集合ObservableList
什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...
- Unity应用架构设计(2)——使用中介者模式解耦ViewModel之间通信
当你开发一个客户端应用程序的时候,往往一个单页会包含很多子模块,在不同的平台下,这些子模块又被叫成子View(视图),或者子Component(组件).越是复杂的页面,被切割出来的子模块就越多,子模块 ...
随机推荐
- hive网站日志数据分析
一.说在前面的话 上一篇,楼主介绍了使用flume集群来模拟网站产生的日志数据收集到hdfs.但我们所采集的日志数据是不规则的,同时也包含了许多无用的日志.当需要分析一些核心指标来满足系统业务决策的时 ...
- Day4-生成器generator
列表生成式 需求:列表[0,1,2,3,4,5,6,7,8,9]每个值加1,如何实现? 方法1:列表追加 >>> a = [0,1,2,3,4,5,6,7,8,9] >> ...
- 第1 章MySQL 基本介绍
第 1 章 MySQL 基本介绍 前言: 作为最为流行的开源数据库软件之一,MySQL 数据库软件已经是广为人知了.但是为了照顾对MySQL还不熟悉的读者,这章我们将对 MySQL 做一个简单的介 ...
- javase基础回顾(四) 自定义注解与反射
本篇文章将从元注解.自定义注解的格式.自定义注解与反射结合的简单范例.以及自定义注解的应用来说一说java中的自定义注解. 一.元注解 元注解也就是注解其他注解(自定义注解)的java原生的注解,Ja ...
- R + ggplot2 Graph Catalog(转)
Joanna Zhao’s and Jenny Bryan’s R graph catalog is meant to be a complement to the physical book,Cre ...
- ASP.NET 开发者 开始学习ASP.NET Core 2吧
. NET Core 从2016年6月28日发布,过去了将近一年的时间,但是在工作中发现大家对.net core的接受程度并不高,这只是一个感觉,俗话说“没有调查就没有发言权”, 这两天通过微信小程 ...
- 使用DotNetty编写跨平台网络通信程序
长久以来,.Net开发人员都非常羡慕Java有Netty这样,高效,稳定又易用的网络通信基础框架.终于微软的Azure团队,使用C#实现的Netty的版本发布.不但使用了C#和.Net平台的技术特点, ...
- js的for循环闭包问题
一个简单的例子,如果想循环输出数组中的每一个数值我们可以利用for循环来输出例如: <script type="text/javascript"> var arr=[& ...
- Linux SSH安全技巧
SSH服务器配置文件是/etc/ssh/sshd_conf.在你对它进行每一次改动后都需要重新启动SSH服务,以便让改动生效. 1.修改SSH监听端口默认情况下,SSH监听连接端口22,攻击者使用端口 ...
- 为什么使用bootstrap在一个页面同时做两个轮播效果时,只有第一个有效??
我们都知道使用bootstrap做轮播效果非常快,但是有时候一个页面会需要两个轮播:但这个时候再次使用bootstrap做轮播效果时就会失效:原因在于bootstrap的Carousel问题,只要修改 ...