声明:本文为笔者原创,但首发于InfoQ中文站,详见文末声明
日前发布的dojo 1.7版本对其源码进行了很大的变更。在迈向2.0版本之际,dojo提供了许多新的功能,也对许多已有的功能进行了修改,具体来说新版本更好地支持AMD规范、提供了新的事件处理系统(计划在2.0版本中替换dojo.connect API)和DOM查询模块、更新对象存储相关接口(ObjectStore)等。在本文中我们将会介绍在dojo 1.7版本中新增的面向方面编程(AOP)功能以及其实现原理。
1.
简介
AOP即面向方面编程,是面向对象编程思想的延续。利用该思想剥离一些通用的业务,可以有效降低业务逻辑间的耦合度,提高程序的可重用性。随着Java领域中Spring框架的流行,其倡导的AOP理念被更多的人所熟识(要注意的是Spring并不是该思想的首创者,但说Spring框架的流行让更多人了解和学习了AOP思想并不为过)。因为Java为静态语言,所以在实现AOP功能时较为复杂,一般采取两种方式即动态代理和字节码生成技术(如CGLib)来实现该功能。在JavaScript领域,因为以前模块化需求并不是很强烈,所以AOP的理念并没有被广泛引入进来。但是随着RIA技术的发展,越来越多的业务逻辑在前台完成,JavaScript代码的组织和可维护性越发重要,正是在这样的背景下,出现了很多JavaScript模块化管理的类库,而dojo也在这方面积极探索,新版本的dojo已经更好地支持AMD规范,并提供了面向方面编程的支持。
在面向方面编程功能推出之前,dojo可以通过使用connect方法来实现类似的功能。connect方法主要可以实现两类功能即为dom对象绑定事件和为已有的方法添加后置方法。已经有不少文章分析dojo的connect方法的使用和原理,再加上dojo计划在将来版本中移除该API,所以在此不对这个方法进行更细致的分析了。
2. dojo的aspect模块使用简介
在dojo 1.7的版本中新增了aspect模块,该模块主要用来实现AOP的功能。借助于此项功能可以为某个对象的方法在运行时添加before、after或around类型的增强(advice,即要执行的切面方法)。为了介绍此功能,我们先用dojo的类机制声明一个简单的类:
define("com.levinzhang.Person",["dojo/_base/declare"],function(declare){
declare("com.levinzhang.Person", null,{
name:null,
age:null,
constructor: function(name,age){
this.name = name;
this.age = age;
},
getName:function(){
return this.name;
},
getAge:function(){
return this.age;
},
sayMyself:function(){
alert("Person's name is "+this.getName()+"!");
}
});
})
这里声明类的方法,与我们在介绍类机制时略有不同,因为dojo从1.6版本开始支持AMD规范,通过define方法来声明模块及其依赖关系。有了类以后,我们需要创建一个实例,如下:
dojo.require("com.levinzhang.Person");
var person = new com.levinzhang.Person("levin",30);
现在我们要借助dojo的aspect模块为这个类的实例添加AOP功能。假设我们需要在sayMyself方法的调用前后分别添加对另一个方法的调用(即所谓的增强advice),示例代码如下:
var aspect = dojo.require("dojo.aspect");//引入aspect模块
//声明在person 的sayMyself方法调用前要调用的方法
var signal = aspect.before(person,"sayMyself",function(){
alert("调用了before");
});
//声明在person 的sayMyself方法调用后要调用的方法
aspect.after(person,"sayMyself",function(){
alert("调用了after");
});
//此时调用sayMyself方法将会先后打印出:
//“调用了before”、“Person's name is levin!”、“调用了after”
//即按照before、目标方法、after的顺序执行
person.sayMyself();
在以上的代码片段中,我们使用了aspect的before和after方法实现了在目标方法前后添加advice。在调用before和after方法后将会返回一个signal对象,这个对象记录了目标advice并提供了移除方法,如要移除上文添加的before
advice,只需执行以下代码:
signal.remove();//移除前面添加的beforeadvice
//此时调用sayMyself方法将会先后打印出:
// “Person's name is levin!”、“调用了after”
//即通过aspect.before添加的方法已经被移除
person.sayMyself();
除了before和after类型的advice,dojo还支持around类型的advice,在这种情况下,需要返回一个function,在这个function中可以添加任意的业务逻辑代码并调用目标方法,示例代码如下:
var signal = aspect.around(person, "sayMyself", function(original){
return function(){
alert("before the original method");
original.apply(person,arguments);//调用目标方法,即原始的sayMyself方法
alert("after the original method");
}
});
//此时调用sayMyself方法将会先后打印出:
//“before the original method”、“Person's name is levin!”、“after the original method”
person.sayMyself();
从上面的示例代码我们可以看到,around类型的advice会有更多对业务逻辑的控制权,原始的目标方法会以参数的形式传递进来,以便在advice中进行调用。
通过对以上几种类型advice使用方式的介绍,我们可以看到dojo的AOP功能在JavaScript中实现了AOP Alliance所倡导的advice类型。需要指出的是,每种类型的advice均可添加多个,dojo会按照添加的顺序依次执行。
3.
实现原理
了解了dojo AOP功能的基本语法后,让我们分析一下其实现原理。dojo aspect模块的实现在dojo/aspect.js文件中,整个文件的代码数在100行左右,因此其实现是相当简洁高效的。
通过var aspect
= dojo.require("dojo.aspect");方法引入该模块时,会得到一个简单的JavaScript对象,我们调用aspect.before、aspect.around、aspect.after时,均会调用该文件中定义的aspect方法所返回的function。
define([], function(){
……
return {
before: aspect("before"),
around: aspect("around"),
after: aspect("after")
};
});
现在我们看一下aspect方法的实现:
function aspect(type){
//对于不同类型的advice均返回此方法,只不过type参数会有所不同
return function(target, methodName, advice, receiveArguments){
var existing = target[methodName], dispatcher;
if(!existing || existing.target != target){
//经过AOP处理的方法均会被一个新的方法所替换,也就是这里的dispatcher
dispatcher = target[methodName] = function(){
// before advice
var args = arguments;
//得到第一个before类型的advice
var before = dispatcher.before;
while(before){
//调用before类型的advice
args = before.advice.apply(this, args) || args;
//找到下一个before类型的advice
before = before.next;
}
//调用around类型的advice
if(dispatcher.around){
调用dispatcher.around的advice方法
var results = dispatcher.around.advice(this, args);
}
//得到第一个after类型的advice
var after = dispatcher.after;
while(after){
//调用after类型的advice
results = after.receiveArguments ? after.advice.apply(this, args) || results :
after.advice.call(this, results);
//找到下一个after类型的advice
after = after.next;
}
return results;
};
if(existing){
//设置最初的around类型的advice,即调用目标方法
dispatcher.around = {advice: function(target, args){
return existing.apply(target, args);
}};
}
dispatcher.target = target;
}
//对于不同类型的advice,通用advise方法来修改dispatcher,即对象的同名方法
var results = advise((dispatcher || existing), type, advice, receiveArguments);
advice = null;
return results;
};
}
我们可以看到,在第一次调用aspect方法时,原有的目标方法会被替换成dispatcher方法,而在这个方法中会按照内部的数据结构,依次调用各种类型的advice和最初的目标方法。而构建和调整这个内部数据结构是通过advise方法来实现的:
function advise(dispatcher, type, advice, receiveArguments){
var previous = dispatcher[type];//得到指定类型的前一个advice
var around = type == "around";
var signal;
if(around){
//对around类型的advice,只需调用advice方法,并将上一个advice(有可能即为//目标方法)作为参数传入即可
var advised = advice(function(){
return previous.advice(this, arguments);
});
//构建返回的对象,即aspect.around方法的返回值
signal = {
//移除方法
remove: function(){
signal.cancelled = true;
},
advice: function(target, args){
//即为真正执行的around方法
return signal.cancelled ?
previous.advice(target, args) : //取消,跳至下一个
advised.apply(target, args); // 调用前面的advised方法
}
};
}else{
// 对于after或before类型的advice,构建移除方法
signal = {
remove: function(){
var previous = signal.previous;
var next = signal.next;
if(!next && !previous){
delete dispatcher[type];
}else{
if(previous){
previous.next = next;
}else{
dispatcher[type] = next;
}
if(next){
next.previous = previous;
}
}
},
advice: advice,
receiveArguments: receiveArguments
};
}
if(previous && !around){
if(type == "after"){
//将新增的advice加到列表的尾部
var next = previous;
while(next){
//移到链表尾部
previous = next;
next = next.next;
}
previous.next = signal;
signal.previous = previous;
}else if(type == "before"){
//将新增的advice添加到起始位置
dispatcher[type] = signal;
signal.next = previous;
previous.previous = signal;
}
}else{
// around类型的advice或第一个advice
dispatcher[type] = signal;
}
return signal;
}
以上,我们分析了dojo的aspect模块的使用以及实现原理,尽管这种将静态语言编程风格移植到脚本语言中的做法能否被大家接受并广泛使用尚有待时间的检验,但这种尝试和实现方式还是很值得借鉴的。
参考资料:
http://livedocs.dojotoolkit.org/
http://dojotoolkit.org/
声明:
本文已经首发于InfoQ中文站,版权所有,原文为《dojo1.7功能介绍:面向方面编程(AOP)功能与原理》,如需转载,请务必附带本声明,谢谢。
InfoQ中文站是一个面向中高端技术人员的在线独立社区,为Java、.NET、Ruby、SOA、敏捷、架构等领域提供及时而有深度的资讯、高端技术大会如QCon 、线下技术交流活动QClub、免费迷你书下载如《架构师》等。
分享到:
相关推荐
`dojo/aspect`模块提供了面向切面编程(AOP)的支持,允许在方法调用前、后或异常时插入额外的行为,增强了代码的可扩展性和可维护性。 七、dojo/data Dojo数据接口(如ItemFileReadStore和ItemFileWriteStore)...
8. **dojo/aspect**: 这是一个面向切面编程(AOP)的模块,用于在方法调用前后插入额外的功能,如日志记录、性能分析等,而不改变原有代码结构。 9. **dojo/i18n**: 国际化支持是Dojo的一个重要特性,`dojo/i18n`...
【Dojo 1.7 中文版本注释功能说明】 Dojo 1.7 是一个重大的版本更新,其中引入了显著的改进和新特性,尤其是对模块加载机制的革新。这一版本的核心亮点是实现了Asynchronous Module Definition (AMD)机制,这是一项...
【Dojo 1.7 知识点详解】 Dojo 1.7 是 Dojo Toolkit 重要的版本更新,其中引入了显著的改进和新特性,尤其是对模块加载机制的革新。这一版本的主要焦点在于Asynchronous Module Definition (AMD),这是一种优化...
在Dojo 1.7 中,这些功能被重新组织并模块化,使得开发者可以根据需要按需加载,显著提高了页面加载速度。AMD模块化系统的引入是这一版本的一大亮点,它允许异步加载和依赖管理,使得代码结构更加清晰,也更利于维护...
7. **dojo/aspect**:提供面向切面编程(AOP)的支持,允许在方法调用前、后或替代方法执行插入额外的功能。 8. **dojo/data**:提供了一种数据模型接口,用于与各种数据源进行交互,比如从服务器获取数据。 9. **...
<<Dojo的高级运用:Widget的制作>> 和 使用Dojo和JSON构建Ajax应用>> 中涉及到的源代码 博文链接:https://tailsherry.iteye.com/blog/102907
- `dojo.event`:事件驱动API,支持面向切面编程和事件队列。 - `dojo.back`:撤销/重做功能的栈管理器。 - `dojo.rpc`:与服务器进行通信,特别是JSON格式的Web服务。 - `dojo.colors`:颜色处理工具。 - `dojo....
Dojo 是一个强大的JavaScript工具包,它为Web开发提供了丰富的功能和组件,涵盖了从DOM操作到数据管理,从动画效果到AJAX通信等各个方面。在Dojo 1.10版离线参考手册中,我们可以深入了解到这个版本的详细信息和使用...
如dojo.io用于不同类型的IO传输,dojo.dnd提供拖放功能的API,dojo.string提供了字符串处理方法,dojo.date帮助解析和操作日期,dojo.event处理事件驱动和AOP开发,dojo.back管理撤销操作的栈,dojo.rpc用于与后端...
Dojo 是一个强大的JavaScript工具库,它为Web开发提供了丰富的功能和组件,涵盖了从DOM操作、事件处理到AJAX通信、动画效果等各个方面。在深入理解Dojo之前,我们需要了解JavaScript在网页开发中的核心地位以及它...
10. **dojo/aspect**:面向切面编程(AOP)模块,允许在特定方法调用前后插入额外的行为,增强了代码的可维护性和扩展性。 Dojo 1.4.0版本的tar文件解压后,通常会包含源码、文档、示例、测试用例等多个目录,方便...
- `dojo.event`:实现了事件驱动API,支持面向切面编程。 - `dojo.back`:提供操作撤销功能。 - `dojo.rpc`:与后端服务交互,例如通过JSON协议。 - `dojo.colors`:颜色管理工具。 - `dojo.data`:统一的数据...
* dojo.event:事件驱动的 API,支持 AOP 开发,以及主题和队列的功能。 * dojo.back:撤销用户操作的栈管理器。 * dojo.rpc:与后端服务进行通信,支持 JSON 语法的 Web 服务。 * dojo.colors:颜色工具包。 * dojo...
8. **dojo/aspect**: 用于面向切面编程(AOP),可以方便地插入或拦截方法调用。 9. **dijit**: Dojo的UI组件库,包含了大量的可复用的、响应式的用户界面控件,如按钮、表单、布局容器等。 10. **dojox**: 扩展库...
Dojo API中文参考手册是一个面向初学者的指导性文件,它详细介绍了Dojo框架的体系结构、常用包及功能,并通过附加注释实例帮助开发者理解和使用Dojo。以下是根据给定内容整理的知识点: 1. Dojo体系架构分层: - ...
通过两天的学习,参与者不仅能够熟悉 JavaScript 的基础语法和面向对象编程,还能够深入了解并掌握 Dojo 框架的核心概念和技术细节。此外,课程中还包括了实用的工具介绍和丰富的实践环节,旨在帮助学员能够迅速上手...
从给定的文件标题“dojo中文文档分析与介绍”及部分描述中,我们可以提炼出关于Dojo框架的多个关键知识点,下面将详细展开这些知识点: ### Dojo框架概述 Dojo是一个开源的JavaScript库,专注于简化DHTML应用程序...