答案:许多 Java 开发人员已经接受了面向方面编程(AOP)的非强制性风格和灵活性,特别是在用于建立高度松散和可扩展的企业系统时。在本文中,您将看到 AOP 的功能设计概念之一(静态横切)如何把可能是一大堆混乱的紧密耦合的代码转变成一个强大的、可扩展的企业应用程序。
在将商业需求转换成软件功能的快速开发周期中,面向方面是一种可以利用的强有力的设计原理。通过将首要的设计重点从面向对象编程(OOP)的传统化特性转移出来,AOP 和设计原理允许软件架构师用一种和面向对象相当而又互补的方式来考虑设计。
在本文里,您将学习如何实现 AOP 中最没有得到充分利用的特性之一。横切(crosscutting)是一种蕴含强大力量的相对简单的设计和编程技术,尤其是当它被用于建立松散耦合的、可扩展的企业系统时。虽然动态横切(其中对象的运行时行为可以改变)被认为是 AOP 的根基之一,但静态横切却是一种远不为人所知的技术。我将在本文中尝试弥补这个缺憾。我将首先概述动态和静态横切,然后迅速切入一个实现场景来展示后一种技术。您将亲自体会到静态横切是多么方便地克服了如下这个最常见的企业挑战之一:如何在利用第三方代码的同时保持应用程序代码库(codebase)的灵活性。
请注意,尽管我首先简要地从概念上概述面向方面编程,但本文并不是一篇关于 AOP 的介绍。请参阅 参考资料 一节以获得关于该主题的介绍性文章的列表。
AOP 概述
面向对象设计最根本的魅力在于,它能够将真实世界领域中的实体及各自的行为建模为抽象的对象。以面向对象方式设计的系统产生了很多有效的业务对象,比如 Person、Account、Order 以及 Event。面向对象设计的缺点在于,这样的业务对象会因为混合的属性和与对象最初意图不一致的操作而变得混乱。
通过使设计者运用动态和静态横切,用一种非强制性的整洁和模块化的方法来添加对象行为,面向方面编程有效地解决了这一问题。
什么是横切?
横切 是面向方面编程的专有名词。它指的是在一个给定的编程模型中穿越既定的职责部分(比如日志记录和性能优化)的操作。在横切的世界里,横切有两种类型:动态横切和静态横切。在本文中,尽管我将简要地同时讨论二者,但我主要关注静态横切。
动态横切
动态横切 是通过 切入点 和 连接点 在一个 方面 中创建行为的过程,连接点可以在执行时横向地应用于现有对象。动态横切通常用于帮助向对象层次中的各种方法添加日志记录或身份认证。下面让我们花点时间了解一下动态横切中的一些实际概念:
方面(aspect)类似于 Java 编程语言中的类。方面定义切入点和通知(advice),并由诸如 AspectJ 这样的方面编译器来编译,以便将横切(包括动态的和静态的)织入(interweave)现有的对象中。
一个 连接点(join point) 是程序执行中一个精确执行点,比如类中的一个方法。例如,对象 Foo 中的方法 bar() 就可以是一个连接点。连接点 是个抽象的概念;不用主动定义一个连接点。
一个 切入点(pointcut) 本质上一个用于捕捉连接点的结构。例如,可以定义一个切入点来捕捉对对象 Foo 中的方法 bar() 的所有调用。和连接点相反,切入点需要在方面中定义。
通知(advice) 是切入点的可执行代码。一个经常定义的通知是添加日志记录功能,其中切入点捕捉对对象 Foo 中的 bar() 的每个调用,然后该通知动态地插入一些日志记录功能,比如捕捉 bar() 的参数。
这些概念是动态横切的核心,虽然正如我们即将看到的,它们并不全都是静态横切所必需的。请参阅 参考资料 来了解关于动态横切的更多内容。
静态横切
静态横切 和动态横切的区别在于它不修改一个给定对象的执行行为。相反,它允许通过引入附加的方法字段和属性来修改对象的 结构。此外,静态横切可以把扩展和实现附加到对象的基本结构中。
虽然现在还无法谈及静态横切的普遍使用——它看起来是 AOP 的一个相对未被探索(尽管非常具有吸引力)的特性——然而这一技术蕴含的潜力是巨大的。使用静态横切,架构师和设计者能用一种真正面向对象的方法有效地建立复杂系统的模型。静态横切允许您不用创建很深的层次结构,以一种本质上更优雅、更逼真于现实结构的方式,插入跨越整个系统的公共行为。
在本文剩下的篇幅中,我将重点讲解静态横切的技术和应用。
创建静态横切
创建静态横切的语法和动态横切有很大的不同,即没有切入点和通知。给定一个对象(比如下面定义的 Foo),静态横切使得创建新方法、添加附加的构造函数,甚至改变继承层次都变得十分简单。我们将用一个例子来更好地展示静态横切是怎样在一个现有的类中实现的。清单 1 显示了一个简单的没有方面的 Foo。
清单 1. 没有方面的 Foo
public class Foo {
public Foo() {
super();
}
}
如清单 2 所示,在一个对象中添加一个新的方法和在一个方面中定义一个方法是同样简单的。
清单 2. 向 Foo 添加一个新方法
public aspect FooBar {
void Foo.bar() {
System.out.println("in Foo.bar()");
}
}
构造函数略有区别的地方在于 new 关键字是必需的,如清单 3 所示。
清单 3. 向 Foo 添加一个新的构造函数
public aspect FooNew {
public Foo.new(String parm1){
super();
System.out.println("in Foo(string parm1)");
}
}
改变对象的继承层次需要一个 declare parents 标签。比如,为了变成多线程的,Foo 将需要实现 Runnable,或者扩展 Thread。清单 4 显示了用 declare parents 标签来改变 Foo 的继承层次。
清单 4. 改变 Foo 的继承层次
public aspect FooRunnable {
declare parents: Foo implements Runnable;
public void Foo.run() {
System.out.println("in Foo.run()");
}
}
现在,您可能开始独自设想静态横切的含意了,特别是在与创建松散耦合、高度可扩展的系统有关时。在下面的几小节中,我将带您看一个一个真实的设计和实现场景,以展示使用静态横切来扩展您的企业应用的灵活性是多么容易。
实现场景
企业系统经常被设计来利用第三方的产品和库。为了不把整个结构和所需产品耦合在一起,通常在设计来与外部厂商代码交互的应用中包括进一个抽象层。在插入其他厂商的实现乃至自主开发的代码时,这个抽象层在对系统的一致性产生最小破坏的情况下,为该体系结构提供了高度的灵活性。
在这个实现场景中,设想系统在某一操作发生后,通过各不相同的通讯渠道通知客户。这个例子系统使用了一个 Email 对象来代表直接电子邮件通讯的一个实例。如清单 5 所示,Email 对象包含了诸如发件人地址、收件人地址、主题栏和消息正文等属性。
清单 5. 例子 Email 对象
public class Email implements Sendable {
private String body;
private String toAddress;
private String fromAddress;
private String subject;
public String getBody() {
return body;
}
public String getFromAddress() {
return fromAddress;
}
public String getSubject() {
return subject;
}
public String getToAddress() {
return toAddress;
}
public void setBody(String string) {
body = string;
}
public void setFromAddress(String string) {
fromAddress = string;
}
public void setSubject(String string) {
subject = string;
}
public void setToAddress(String string) {
toAddress = string;
}
}
整合第三方代码
除了建立一个发送电子邮件、传真、短消息等的自定义通讯系统之外,体系结构团队决定整合进一个供应商的产品,该产品能遵循特定的规则,发送基于任意对象的消息。该产品非常灵活,并且通过 XML 提供了一个映射机制,允许将自定义客户端对象映射到与厂商的特定渠道实现。该厂商的系统严重依赖于这一映射文件和 Java 平台的反射能力来与普通 Java 对象协同工作。为了体现灵活性,体系结构团队建立了一个 Sendable 接口模型,如清单 6 所示。
清单 6. 例子 Sendable 接口
public inte易做图ce Sendable {
String getBody();
String getToAddress();
}
图 1 显示了 Email 对象和 Sendable 接口的类图。
图 1. Email 和 Sendable 的类图
设计挑战
除了通过不同渠道发送各种格式的消息的能力之外,供应商通过一个已给的接口,提供一个钩子(hook)来允许进行收件人地址验证。供应商的文档表明,实现这个接口的任何对象都将遵循一个预定义的生命周期,它 validateAddress() 方法将被调用,并正确地处理相应的结果行为。如果 validateAddress() 返回 false,供应商的通讯系统将不再试图进行相应的通讯。清单 7 显示了供应商的 validateAddress() 接口。
清单 7. Sendable 接口的地址验证
package com.acme.validate;
public inte易做图ce Validatable {
boolean validateAddress();
}
运用基本的面向对象设计原理,体系结构团队决定修改 Sendable 接口来扩展供应商的 Validatable 接口。但是,这一决定将导致对供应商代码的直接依赖和耦合。如果开发团队接下来决定用另一个供应商的工具,就不得不重构代码库,以删除 Sendable 接口中的 extends 语句和对象层次中已实现的行为。
一个更优雅且
上一个:·源代码搜索引擎-Koders.com
下一个:web应用分页技术