写点什么

dojo1.7 功能介绍:面向方面编程(AOP)功能与原理

  • 2012-01-04
  • 本文字数:4994 字

    阅读完需:约 16 分钟

日前发布的 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 模块的使用以及实现原理,尽管这种将静态语言编程风格移植到脚本语言中的做法能否被大家接受并广泛使用尚有待时间的检验,但这种尝试和实现方式还是很值得借鉴的。

参考资料

关于作者

张卫滨,关注企业级 Java 开发和 RIA 技术,个人博客: http://lengyun3566.iteye.com ,微博: http://weibo.com/zhangweibin1981


感谢崔康对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2012-01-04 01:245152

评论

发布
暂无评论
发现更多内容

专访丨用友网络副总裁邹达:如何应对创新型数字化挑战?

YonBuilder低代码开发平台

腾讯WeTest微信小程序上线啦!产品资讯一手掌握!

WeTest

如何以卫语句取代嵌套条件表达式

华为云开发者联盟

条件表达式 卫语句 嵌套条件表达式 代码结构

想减少代码量,快设置一个有感知的 Aware Spring Bean

华为云开发者联盟

spring bean Aware 接口

机器人流程自动化评估体系全面助力垂直行业智能化转型

王吉伟频道

RPA 机器人流程自动化 信通院

软件开发中的风险如何处理?

源字节1号

微信小程序 软件开发

Module Federation在客服工单业务中的最佳实践

得物技术

前端 Module 模块 iframe Federation

中国设计师品牌Le Arome乐欧幕靠什么做到爆款10分钟售罄?

科技大数据

在APICloud开发平台使用友盟统计功能教程

YonBuilder低代码开发平台

APP开发 APICloud 友盟

龙蜥开发者说:学无止境的 Linux ,以及我的第一个定制版本发布之路 | 第4期

OpenAnolis小助手

Linux 龙蜥社区 开发者说 宝贵经历

Pulsar—新一代云原生消息平台

中原银行

分布式 pulsar 中原银行 分布式消息

低代码平台常见的安全隐患,J2PaaS低代码平台如何解决?

J2PaaS低代码平台

低代码开发 低代码平台 企业级低代码平台 J2PaaS低代码平台

如何在 Zadig 上玩转自动化测试,为业务质量保障提供最大价值

Zadig

云原生 软件测试 CI/CD 软件交付

TASKCTL产品安装常见问题

TASKCTL

分布式 kettle ETL ETL任务 调度任务

接口自动化的关键思路和解决方案,本文全讲清楚了

Liam

Jmeter Postman API 测试工具 接口自动化测试

能让程序员涨薪5K的Hystrix核心工作原理,你真的不打算学吗?

Java工程师

Java 程序员 互联网 微服务 科技

【IT运维】国内优秀的IT运维企业有哪些?

行云管家

云计算 运维 网络运维 IT运维

企业怎样有效地进行文档管理

小炮

企业 文档管理

Zadig 基于 OPA 实现 RBAC 和 ABAC 权限管理技术方案详解

Zadig

云原生 CI/CD 软件交付

Flink 在众安保险金融业务的应用

Apache Flink

大数据 flink 编程 流计算 实时计算

浅谈Java虚拟机(HotSpot)的内存回收相关细节

CRMEB

为什么企业对私有化部署IM如此青睐有加?

WorkPlus

领域驱动设计(DDD)理论与方法

Java工程师

Java 程序员 互联网 DDD 架构设计

玩转LiteOS组件:Openexif

华为云开发者联盟

LiteOS Huawei LiteOS Openexif Exif JPEG文件

EMAS隐私合规检测专项服务,从确保形式合规及实质合规规避风险

移动研发平台EMAS

阿里云 开发 数据安全 移动开发 隐私合规

结合实际案例谈谈项目管理经验

云智慧AIOps社区

学习 项目管理 pmp 软考 沟通技巧

政企上云网络适配复杂,看华为云Stack有妙招

华为云开发者联盟

数据中心 云网络 华为云Stack 政企上云 L3GW服务

行云管家荣膺《中国网络安全行业全景图(第九版)》收录

行云管家

网络安全 行云管家 安全牛

为什么都是技术合伙人被踢出局?

方云AI研发绩效

团队管理 研发管理 CTO SaaS

jackson学习之三:常用API操作

程序员欣宸

4月月更

3月月更中奖名单新鲜出炉!快来看有没有你呀!

InfoQ写作社区官方

3月月更 热门活动

dojo1.7功能介绍:面向方面编程(AOP)功能与原理_JavaScript_张卫滨_InfoQ精选文章