ITPub博客

首页 > Linux操作系统 > Linux操作系统 > AOP@Work: AOP 和元数据2

AOP@Work: AOP 和元数据2

原创 Linux操作系统 作者:lt-2046 时间:2008-01-04 10:12:23 0 删除 编辑
在这篇由两部分组成的关于组合使用元数据和 AOP 的系列文章的第二部分中,作者及 AOP 实践者 Ramnivas Laddad 将推荐一种把元数据视为多维关注点空间中的签名的全新方法。他还将介绍有效组合使用元数据与 AOP 的一组准则,并讨论元数据注释将如何影响面向方面的编程的应用。

本文的第一部分 中,我介绍了新的 Java 元数据功能,并说明了如何以及在什么地方可以用元数据注释 最有效地增强 AOP 的连接点模型。我还概括三种最重要 AOP 系统对元数据的 已有支持,这三种系统是:AspectWerkz、AspectJ 和 JBoss AOP。在第一部分中,还介绍了元数据对 AOP 系统的模块性的影响,最后,还重新构建了一个例子,以说明 如何在 AOP 系统中逐步添加元数据注释。

在本文的第二部分中,我将介绍一种全新的方法,该方法将元数据看成通往多维签名空间的一种途径。 这种方法具有分解元素签名的实际用途,并且在总体上对设计注释类型很有用, 甚至对于那些不从事 AOP 的开发人员也很有帮助。在本文的第二部分中,我仍然将重点讨论元 数据方法在面向对象的编程中的最有效用法。其中,我将展示一种用 AOP 有效减少 使用元数据注释所带来的模块性损失的用法。在本文的最后,我将提供一组确定什么时候以及如何最佳利用元数据的准则。我还会考虑添加元数据对采用 AOP 的影响。

主导签名的专制

程序元素的签名,如类型、方法或者由几个组件组成的字段,这些组件包括元素名、访问规范、基本类型、 方法参数、异常规则等。并不是每种程序元素都要使用所有组件,但是 不管在什么情况下,程序元素的名字是最有价值的东西,特别是当它对外公开时, 因为它会通知其使用者该元素的作用。

如果不使用元数据编程,那么每个程序元素只能使用一个名字。在这种情况下,常见的作法 是将程序元素按它们的主要或者首要功能命名。例如,请看下面 credit 方法的签名:

public void credit(float amount);

这个方法名只反映了方法的一个属性:执行信贷业务的业务逻辑。该元素的所有 其他功能都丢失了。虽然这种签名可以告诉使用这个方法的客户该元素的主要功能,但是它无法通知其他客户 —— 那些实现横切关注点的客户,比如安全和事件管理客户。 这就是主导签名的专制,由只使用一个名称来表示元素的限制所导致的。

在 AOP 研究文献中经常会讨论到类似的情况:主要分解(decomposition)的专制, 其中主要的关注点(通常是业务逻辑)支配了类的设计,特别是继承层次(inheritance hierarchy)。

关于本系列

AOP@Work 系列是为具有一定面向方面的编程背景、并准备扩展或者加深他们的知识的开发人员准备的。与大多数 developerWorks 文章一样,本系列具有很高实用性:从每一篇文章中学到的知识立刻就能使用得上。

所挑选的为这个系列撰稿的每一位作者,都在面向方面编程方面处于领导地位或者拥有这方面的专业知识。许多作者都是本系列中讨论的项目或者工具的开发人员。每篇文章都经过仔细审查,以确保所表达的观点的公平和准确。

关于文章的意见和问题,请直接与文章的作者联系。如果对整个系列有意见,可以与本系列组织者 Nicholas Lesiecki 联系。更多关于 AOP 的背景知识,请参阅 参考资料






签名纠结

确保元素的签名与其横切功能匹配的一种方法是修改签名的名称,使它反映所有功能。 例如,还需要在某个事务中运行并获得认证的信贷业务可以具有以下签名:

public void transactional_authorized_credit(float amount);

在看到像上面的这样的名字时,确实能让您想到方法的横切事务和认证 要求,但是这会使代码变得很糟糕,只有很少的开发人员会使用它。事实上,只有在 特殊环境中才会看到这种名字,比如使用特殊的命名约定将方法标识为内部消费。

随着系统的发展,上面的方法还会带来一个严重的问题。如果程序元素的横切特性 发生改变,那么它的名字和它的所有引用者都需要做相应的改变。例如,如果在上面的例子中, 系统的发展使得信贷业务不再需要认证,那么这个方法名就需要改为 transactional_credit(),而这个方法的所有调用都要做相应的 改变。

这种方法的另一个问题是组合使用来自多关注点的元素的名称增加了调用者的识别负担。例如, 上述方法的业务调用者对该方法的其他特性并不感兴趣,但是仍然 要受其他关注点的影响。

如果熟悉 AOP,那么您已经知道代码纠结 —— 将多关注点代码混合到一个模块中 —— 会导致难于理解和难于维护的代码。签名纠结 与此类似。就像在上面的例子中, 当程序元素的名字反映其在多个关注点中的作用时,我们就称之为纠结的签名。

在签名纠结与意义不明的签名(忽略程序元素的其他功能,并简化上面的方法名,例如, credit())之间选择时,大多数开发人员会选择简单性。不过,真正 需要的是第三种选择。在元素签名中加入元数据注释(metadata annotation)可以让您用 系统的方式表达非核心功能。






元数据,帮我解脱出来吧!

利用 Java 5.0 中的元数据功能,可以使用有类型的注释通过编程的方式 传达非核心功能。例如,出于安全考虑,需要在事务管理中执行并获得认证的货款方法 可以加上注释,如下所示:

@Transactional 
@Authorized
public void credit(float amount);

表面上,使用注释与上述签名纠结方法名之间的区别好像并不明显, 然而,我将非核心功能指定从方法名转移到了注释中。这种方法的真正好处 是在调用者这方面,方法的业务客户不需要理解其他关注点。业务客户也不 需要在附加到方法上的注释改变时修改其代码。

除了排除元素签名的纠结外,还可以使用注释来表达与代码的横切关注 相关的任何数据。在这里,注释可以与关注点有关的参数相结合。例如,下面 的方法声明其事务必须具有 Required 语义,并且选中的权限必须是 “accountModification”。

@Transactional(Required)
@Authorized("accountModification")
public void credit(float amount);

可以看出,对于将横切元素的名字分解为多个部分,使用注释很方便。实际上, 注释可以在不使名称交织在一起、或者不增加调用者负担的情况下表示程序元素的非核心功能。






元数据作为一个多维签名

概念上,元数据可以视为额外的维上的值,它使我们可以在多维空间中表示程序元素 的功能。这种理解有助于用系统的方式处理元数据,并使这些数据符合 AOP 的目标。即使 对于不使用面向方面方法的开发人员,从多维的角度看待元数据对设计注释类型也会提供概念上的帮助。

可以将元数据看成以系统性的方式进行多维签名的一种促动因子(enabler):空间中的每一维 都代表一个关注点,每一个注释中的值表示对多维签名空间的一个投射。 考虑刚讨论过的 credit() 方法的签名。图 1 显示在三维签名空间中的 credit() 方法。


图 1. 使用元数据的多维签名空间

图 1 中操作的名字实质上是一维签名空间中的一个值。这个签名空间中的 惟一的维是业务维,而在这一维中操作的值(也称为这一维中的投射)是“credit”。 使用 Transactional 注释、同时将 value 属性设置为 Required,我就可以将这个 例子发展为两维签名空间。值 Required 是这个方法在新增加的事务管理 维中的名字。

极端元数据

可以将多维签名概念延伸到极端,将签名中的每一部分看成是一维。例如, 考虑将签名中如异常规范这样的部分看成是单独的一维。这样,在基于签名的切入点与基于元数据的切入点之间就没有区别了:只有一个使用了扩展签名 的切入点,这个签名由原有签名 和元数据组成。这种概念以及对元数据的这种使用有时称为 explicit programming (请参阅参考资料)。

这种类型的多维签名分解即使不使用 AOP 时也很有用,不过,AOP 实践者会认识到 AOP 解决了同样的横切关注点分解的问题。这是元数据与 AOP 在基本原则上彼此协调的一个例子。

与此类似,现在来考虑一下 Authorized 注释,将 value 属性设置为 “accountModification”。在这个例子中,增加的注释加入了另一维, 从而成为三维签名空间。大多数签名会将重要的值只放入少数维中。这与将两维几何点投身到三维空间中一样:这种投 射的值在其中一维上将为零。

没有注释,元素签名通常会完全忽略非核心维。这种疏忽的主要原因是 没有合适的方法表达它们。新的 Java 元数据功能提供了在每一维中表达 程序功能的精确方式。






将签名空间映射到关注点空间

AOP 系统,如 Hyper/J (现在发展为 Concern Manipulation Environment,请参阅 参考资料)注重关注点空间的多维 视图,利用元数据,我们同时有了一种提供多维签名空间的方法。

在将元数据看成多维签名这种思路中,签名的每位消费者只使用与其关注点相关的 投射。因此,在上面的例子中,将由签名所表示的点投射到业务维会获得名称“credit”。对于业务关注点的实现,重要的就是所执行的操作是 “信贷业务”。业务关注点不知道(或者不需要知道)任何其他维中的值。例如,业务关 注点不需要知道事务属性。

与此类似,当同一操作投射到事务管理维上时,我们就得到值 Required。 现在,从事务管理实现的角度看,这一点在业务维中的值是不重要的 —— 它可以是 creditdebit 或者任何其他值。您可以猜到,对于认证 关注点的实现也是一样:该点在业务和事务管理维中的投射无关紧要。






启用多维接口

延伸多维签名的概念,注释可以用来表示多维接口。与只表示核心维的常规一维接口不同,注释可以有多个接口 —— 应用程序中的每一个关注点有一个。 一个关注点的实现只要考虑投射到相关维中的接口。就像传统接口可以很好地服务于面向对象的 世界视图那样,支持元数据的接口(方面化的接口)也可以服务于面向方面的世界视图。

AOP 实践者都知道,在第一次介绍 AOP 时,常常要说明一个多维分解,该分解类似于用 元数据所进行的多维分解。因此,元数据概念与 AOP 概念非常匹配。 在通过元数据增强的 AOP 实现中,就像平时做的那样,可以将核心关注点映射到类。 区别是现在将系统的横切关注点映射到使用了投射到相关维中的多维接口的方面。

接口示例

参见清单 1 中的 Account 类这个例子。注意元数据 注释如何使将类投射到多维接口更顺利。


清单 1. 包含已注释方法的 Account 类
public class Account {
@Transactional(kind=Required)
@WriteOperation
@Authorization(kind="bankModification")
public void credit(float amount) {
... credit operation business logic
}
@Transactional(kind=Required)
@WriteOperation
@Authorization(kind="bankModification")
public void debit(float amount) {
... debit operation business logic
}
@Transactional(kind=None)
@ReadOperation
@Authorization(kind="bankQuery")
public float getBalance() {
... balance query operation business logic
}
...
}

从业务角度看,对这个接口的投射会映射到以下接口:

public class Account {
public void credit(float amount) {
... credit operation business logic
}
public void debit(float amount) {
... debit operation business logic
}
public float getBalance() {
... balance query operation business logic
}
...
}

上面的接口包括了所有成员,但没有包含注释。投射同一个接口到事务管理维 会得到以下接口:

public class Account {
@Transactional(kind=Required)
* *.*(..)) {
}
@Transactional(kind=Required)
* *.*(..)) {
}
@Transactional(kind=None)
* *.*(..)) {
}
...
}

在该例中,我使用了 AspectJ 通配符符号来表示方法,没有考虑名字、类型、参数等因素。 其他维中的投射与此类似。

其他维

像前面介绍的那样,当业务客户要使用一个方法时,他只需要 理解到业务维的投射即可。例如,AccountServices 类 的 transfer() 方法可以像清单 2 中那样实现。


清单 2. Account 类的业务客户
public class AccountServices {
@Transactional(Required)
public void transfer(Account to, Account from, float amount) {
to.credit(amount);
from.debit(amount);
}
}

除了它所携带的注释(在这里的讨论中可以忽略)之外,这个方法实现没有什么 特别的。特别是,业务客户(transfer() 方法)不知道或者不关心 credit() 或者 debit() 方法的事务维或认证维。

与业务客户一样,事务管理方面只关心它自己的维。(注意,清单 3 对本文第 1 部分的清单 2 中所示的方面进行了重构。)


清单 3. 事务管理方面
public aspect TxMgmt {
public pointcut transactedOps(Transactional tx)
: execution(@Transactional * *.*(..))
&& @this(tx);
Object around(Transactional tx) : transactedOps(tx) {
if(tx.value() == Required) {
...
}
}
}

事务管理方面只使用投射到事务管理方面中的接口。换句话说,不管 所考虑的方法叫 credit() 还是 foo(), 这个方面所关心的都是一样的。

利用元数据描述方面化的接口是一种强大的概念。将元数据考虑为类中的一个方面化接口,通过这种方法来横切系统中的一些关注点,这样,可以将面向对象世界中的一些最佳实践应用到 AOP 中。例如,不会将一个方法命名为 creditUsingJDBC(), 因为希望只描述方法在业务关注空间的功能,而“JDBC”不是这个空间的一部分。 对于方面化的接口也是一样:您可能不想使用名为 ReadLock 的注释类型,这类注释的用途是描述类型的使用。 ReadOperation 是更好的类型,它向外界描述了这个接口。

在 AOP 编程中使用元数据将类与方面之间的耦合限制为附加在程序 元素上的元数据。将这个概念延伸到支持元数据的多维接口时,可以看到每 一个方面都只耦合到相关的接口。因此,方面与类之间的耦合与面向对象的编程 中类之间的耦合没有什么不同。






正确使用元数据的准则

虽然元数据在与 AOP 共同使用时特别有用,但是会有过度使用的危险。过度使用 注释会使包含在程序元素的签名和动态上下文中的原有信息无法得到充分利用。在代码中加入 元数据还会增加代码的复杂性。首先,程序元素必须包含元数据注释。其次,不正确地 在 AOP 中使用元数据会导致 macros on steroids,初看之下,它非常有用,可以节省大量重复的代码,但它会使程序变得 越来越难以理解。因此,除了了解组合使用 AOP 与元数据所涉及的机制之外,掌握组合使用这两种技术所必需的概念和最佳实践也很重要。在这一节中,我将提供一组正确结合 元数据与 AOP 的准则。

1. 如果不需要就不要使用它

如果所考虑的程序元素在其本来的签名和动态上下文中包含捕获所需的连接点的足够信息,那么 就没有理由附加注释。加入了注释并写下切入点来使用这些注释之后,任何没有 注释的元素都不能参与关注点的横切。因此,总是要首先考虑使用元数据的 这些替代方法:

要全局地对待全局关注点:程序元素不应当对关注点(比如在横切实现中进行跟踪和分析)的参与起什么作用。例如,在需要分析的元素上附加 @Profiled 注释没有什么意义。所分析的元素应当是在全局范围内选取的,并且它们的选取取决于当前分析目标。在这种情况下使用注释意味着系统分析目标发生改变时要修改程序 元素。在这种情况下,利用基于包和继承层次结构的签名模式的全局方面可以做得更好。

利用命名约定:如果项目使用良好的命名约定,那么最好在横切时也使用它们。 遵循好的命名约定本身也很有用。例如,可以使用一个 execution(* *.get*()) || execution(boolean *.is*()) 切入点捕获所有 getter 方法的执行,对所有 setter 使用 execution(void *.set*(*))。注意切入点是如何 指定类型和通配符的 —— getter 没有使用任何参数,并且以“is”开始的 getter 返回一个布尔值,而 setter 采用了一个参数并 返回 void。在某些情况下,这种命名风格会捕获错误的元素,比如有一个 settle() 方法,当它返回一个 void 并且采用某一个参数时 (请参阅参考资料中 Sam Pullara 的 blog 对这种情况的介绍)。

在切入点中排除符合这个表达式、但并不需要的连接点通常会更好一些,特别是考虑到忘记 在上面例子中加入 @Getter 和 @Setter 注释的时候。

使用标准 API 模式:标准 API 和扩展点的调用都将获得良好定义,并且可以用使用了通配符的签名模式很容易地捕获这些调用。比如考虑想要捕获 对任意 Swing 组件的所有监听器的调用时。一个使用类似 call(void JComponent+.add*Listener(EventListener+) 这样的方法签名的切入点可能不需要任何注释。这种切入点只在极其例外的条件下才会匹配不需要的方法。 如果使用元数据注释,那么忘记用注释标注方法的可能 (请参阅 @ListenerManagement)要大于捕获不需要的方法。

利用组件和框架边界:组件和框架边界(如对 Web 服务组件、JDBC API 或者 Hibernate API 的调用或者 其扩展点)通常都是经过定义良好的,并且不使用显式元数据就可以很容易地通过切入点表达式捕获它们。下面考虑一个监视远程调用的方面实现。可以通过关注类是否实现 Remote 来很方便地推断远程方法调用,或者用方法签名的异常部分作为参数,像 Remote+.*(..) throws RemoteException 这样的内容。在这种情况下,使用元数据不但 增加了工作的难度,而且还可能导致方法丢失。

这里的核心思想是避免将元数据作为第一选择的诱惑,而是坚持提取程序元素 的签名模式,编写没有元数据的切入点。有了通配符、继承层次、包结构、 好的命名约定以及好的切入点组合,在许多情况下,您可以得到可以接受的切入点。 但要正确地应用这些技术,则必须对所使用的 AOP 系统的切入点语言有很好的了解, 而这种学习的努力是值得的。

2. 使用方面继承

AOP blog 和讨论组中一直在继续的讨论表明,许多开发人员对日志和跟踪例子感 到迷惑。对于这些操作,一般会找到在整个系统中都很稳定的切入点,这意味着可以对 整个系统编写一个方面。不过,在试图对其他横切功能使用同样的方法时, 它变得不可伸缩。如果可以编写一个切入点表达式,那么它会随着系统的发展而变得复杂, 并且通常不很稳定。这会使新来者认为 AOP 的实现是困难和或者不完善的。

技巧在于:当不能为整个系统定义一个签名时,要知道将它分解成小的子系统,并为 每个子系统定义切入点。有很多将系统分解为几部分并找出每个部分的好的签名模式的可能。 前面的准则可以在子系统级别上提供帮助。

使用方面继承首先要创建一个抽象的方面,它包含实现的大部分(通知、内部类型声明等)和几个抽象切入点(请参阅 清单 4)。然后将系统分解为几部分,使得每个子系统都 可以有好的切入点表达式。子系统可以像整个系统那么大,也可以像类那么 小。这种技术是我在 第 1 部分 讨论的 Participant 模式的核心。Participant 模式可以将简单的方面转换 为更复杂的方面,如负责事务管理、线程安全、安全性等方面。

3. 利用为其他目的而存在的注释

方面可以利用服务于不同目的的元数据注释。例如,Enterprise JavaBeans (EJB) 3.0 注释 允许您基于与事务、安全性和方法角色编写切入点(即 remove、init 等)。Hibernate 注释允许您基于表、列名和关系信息编写切入点。 而 Eclipse Metadata Framework (EMF) 允许您基于类之间的关系(如复合、联合、基数等)编写切入点。

4. 使用抽象注释类型

如果在仔细分析要求后,确定使用注释是最好的选择,那么如何使用它们呢? 理想情况是,注释类型应当表示程序元素的抽象功能,而不是它们预期的消费。 这种考虑和实现避免了下面讨论的遗忘损失(loss of obliviousness),因为元素只是提供有关 它们自己的额外信息,没有意识到那些方面。例如,如果将 @ReadOperation 和 @WriteOperation 看作扩展的签名, 那么类就可以保持对读写锁定模式的实现的遗忘。

考虑清单 4 中已实现的基本方面,它遵守了前面讨论的使用方面继承的准则。 (如果需要关于 perthis() 方面的信息, 请参阅 参考资料,这里的重点是定义切入点。)


清单 4. ReadWriteLockSynchronizationAspect 的基本方面
public abstract aspect ReadWriteLockSynchronizationAspect 
perthis(readOperations() || writeOperations()) {
public abstract pointcut readOperations();
public abstract pointcut writeOperations();
private ReadWriteLock lock = new ReentrantReadWriteLock(true);
before() : readOperations() {
lock.readLock().lock();
}
after() : readOperations() {
lock.readLock().unlock();
}
before() : writeOperations() {
lock.writeLock().lock();
}
after() : writeOperations() {
lock.writeLock().unlock();
}
}

清单 5 显示了银行系统的 ReadWriteLockSynchronizationAspect 方面的、元数据驱动 的子方面。


清单 5. 并发 管理子方面
public aspect BankingReadWriteLockSynchronizationAspect 
extends ReadWriteLockSynchronizationAspect {
pointcut lockManagedExecution()
: execution(* banking.Customer.*(..))
|| execution(* banking.Account.*(..));
public pointcut readOperations()
: execution(@ReadOperation * *.*(..))
&& lockManagedExecution();
public pointcut writeOperations()
: execution(@WriteOperation * *.*(..))
&& lockManagedExecution();
}

注意,并不是简单地在所有 read 和 write 操作外面加上锁,因为不是所有类都需要这种并发控制机制。也不应该让类 指出它们的操作是否要用锁管理,因为所选择的类将取决于应用 程序对这些类的使用。(这种实现可能要对 Java 标准库中新的集合类的线程不安全性 负责。)在上面的例子中,lockManagedExecution() 切入点选择了需要用锁管理的连接点子集。

抽象注释类型的能力

有了描述功能的抽象注释后,就可以用同样的注释实现其他关注点。 例如,可以使用 @ReadOperation 和 @WriteOperation 进行脏跟踪和脏缓冲(dirty tracking and caching)。 甚至可以编写加强的方面来确保读操作的控制 流中不会调用对同一对象的写操作!

如果使用 @ReadLock 和 @WriteLock 注释类型“标记”需要用锁管理的方法,那么 这个类会与锁管理功能紧密耦合。而且,将同一个注释用于其他目的很容易出错, 因为注释将要额外考虑特定的关注点,而不是考虑如何表示方法的状态修改特征。

抽象注释类型的更多例子

让我们再讨论几个注释类型的例子。考虑使用 WaitCursor 作为注释类型来模块化等待游标的管理,这类管理是围绕具有这种类型的注释实例的所有方法进行的。可以省略的代码非常多(特别是当考虑每个方法所需要的 try/finally 块时)。不过, 还有使用更能描述功能的注释类型的多种可能。考虑另一种注释类型 Timing,它类似于 meanvariancedistribution 这样的属性,如下所示:

@Timing(mean=5, variance=0.34, distribution=Gaussian) 

可以用几种方法消费这个注释:

  • 进行速率单调性分析(Rate Monotonic Analysis,即 RMA,请参阅参考资料)。
  • 提供运行时间长的操作的反馈(如等待游标)。

当然,这种高度特定的功能表达只在拥有定性的计时数据时才适合。否则,可以使用 定性信息。例如,注释实例可以是 @Timing(value=Long)。这种方案遵守了描述连接点功能 的原则,并提供了下面这些有意义的可能性:

  • 进行选择,只对那些带有某种计时功能的方法进行分析。

  • 检查计时信息的自我一致性,如查找在标记为 Large 值的方法的计算路径中出现的、用 ExtraLong 值标记的方法。

考虑另一个例子,在该例中,方法是用 RetryOnFailure 注释标记的,这样一个方面就可以再次尝试失败 的服务。如果使用 Idempotent 作为注释类型(表示对同一操作的多次调用会产生同样的结果), 那么不仅可以重试失败的操作,而且还可以同时执行多个服务, 如果需要这样的优化的话。

最后,考虑上面的授权和身份验证的例子。权限属性可以是所需权限的 直接表达。对这一概念的更好表述是指定对操作进行分类的一个属性。然后,方面就 可以将分类映射到实际的权限,这些权限在不同的系统之间可以发生改变。例如,一个 系统可能无法从访问控制的角度区分读写操作,而另一些系统则可以对此进行区分。 比这更好的一种方法是使用业务相关的注释类型,如 AccountModification、CustomerInformationAccess 和 Purchase。包含这些类型的注释的程序元素 不仅能被授权和身份验证方面所捕获,而且还可被其他实现捕获,比如 隐私策略增强方面。不过,从头创建这种类型很复杂。一种实用方法是从一个可以工作的入门 级的抽象开始重新构建这种类型,以获得更好的应用。






智慧来源于经验

如果正在实现横切关注点,并且不能遵循上面的准则,那么也可以使用 面向方面的解决方案,即使这意味着“标记”每一个需要一个通知的方法。 使用带标记的 AOP 至少实现了某种模块化,并使您具有获得某种程度的修改实现 的自由度。

标记方法要求使用元数据驱动的切入点编写方面,然后用在切入点 中使用的注释类型来注释程序元素。这种用法类似于使用美化了的宏功能。 虽然通过注释类型,代码使核心元素与方面之间有了强烈的依存关系,但是这样可以节省很多代码(通过将共同代码转移到一个方面或者通知中,而 不是将它放到每一个方法中)。所得到的模块性还有助于限制对方面自 身实现的关注点的修改。

智慧来源于经验。即使从标记方法开始,您也很快就会了解更深入的内容,并 相应地对实现进行修改(通常是重新构建)。还记得您用 OOP 最初做的几个设计 吗?利用您现在掌握的知识,重新评价最初的 OOP 设计,您可能会发现它 们没有达到应有的抽象程度,尽管这种设计仍然好于纯粹的过程式方法。 与此类似,在 AOP 中第一次使用元数据可能会有一个从 OOP 到 AOP 的类似 的短暂过程。






元数据和遗忘

如果不考虑元数据对遗忘的影响,对元数据和 AOP 的讨论就不能说是完整的, 尽管在实践中真正重要的是关注点的模块性而不是遗忘。在 AOP 上下文中, 遗忘属性 要求核心系统不知道横切功能。例如,考虑一个需要在事务中调用某些操作的银行系统。遗忘属性要求核心银行系统不知道是事务管理方面建议进行这些操作。

注释可以有两种表现方式。首先,可以用注释标记元素,这样,切入点就 可以选取注释的元素。该元素实质上是客户驱动的参数,其中,注释只是为了支持切入点而存在。其次,可以将元素标记为它们总是声明其非核心功能,不管是否需要 切入点。这两种方式的区别可能只在于角度的不同,不过: 在后一个例子中,带有注释的元素不知道方面(或者其他元数据消费者), 因而保持了遗忘属性。

更进一步进行分析,当您将元数据视为“方面化的接口”,并遵守好的接口设计通常所采用的 好的做法时,就会自动保持遗忘属性。事实上,保持 遗忘性就是在正确的地方使用正确的注释类型,如在前面准则中所讨论的那样。






元数据与 AOP 采用

恰当地消费元数据是 Java 开发人员在未来几年所面临的有意义的挑战之一。Java 程序的元数据相对来说是一个比较新的概念(即使考虑 XDoclet 所做的努力),而 新的 Java 元数据功能是第一个将元数据一直保留到运行时的实用方法。因此,Java 开发人员需要通过实践来完全了解和优化新功能的使用,不管有没有 AOP。

也就是说,在使用正确的情况下,元数据可以使新人和有经验的开发人员更容易理解 AOP, 并使 AOP 更有可能被广泛采用。结合元数据与 AOP 的好处是双重的:首先,用传统的 切入点语法不能捕获某些具有非核心横切功能的连接点,这一直是令 AOP 的早期使用者头痛的一个问题。虽然像 Participant 模式这样的设计模式可能不真正需要元数据支持就能得到类似的结果, 但是它们并不容易使用。正如我在本文中所展示的,元数据可以方便地捕获这些连接点来充实 横切功能。

在 AOP 中使用元数据对 AOP 的采用带来的第二个好处是它减缓了采用曲线。 即使在当前的初期阶段,元数据也可以成为学习面向方面的编程的某种“培训轮(training wheel)”(正如 Bruce Tate 所说的,请参阅 参考资料)。对于编写第一个 AOP 类型的程序的开发人员而言,道路仍然不是很平坦。经验的欠缺会使在本文中 讨论的准则难以得到遵循,结果可能类似(正如前面提到的)美化的宏。

不过,按这个过程进行是有好处的。当新的范式更方便时,它自然会深入到 连接点签名的内在功能(以及类似 Participant 的模式)中并使用更抽象的元数据。 这种使用表示了元数据使用与 AOP 使用的一种平衡,还可能成为向释放 AOP 终极功能方向迈进的一步。 同时,我们都会摸索出在结合这两种技术时可以遵循的更多最佳实践。






结束语

标准元数据功能提供了表示关于程序元素的额外信息的方便方法。如果使用得当, 这个功能可简化软件系统的创建。虽然有许多利用元数据的方法,但是在与 AOP 结合时它可以工作得最好。在本文中讨论的最佳实践是迈向在 AOP 中充分利用元数据 的第一步。除了准则,我还介绍了一些最适合结合元数据的 AOP 场景,讨论了在三种 最重要的 AOP 系统中对元数据的支持,并对一个 AOP 系统进行系统化的重构,以结合元数据。我还展示了将元数据视为多维关注点空间的签名的一种全新思路, 这是一种与使用 AOP 范式和不使用该规范的开发人员都非常有关的创建元数据类型的用法。

AOP 和元数据的结合就像是天作之合。在 Java 平台上加上标准元数据功能,并在 不同的 AOP 系统上支持它,可以很好排除 AOP 进入主流的最后障碍。不过,就像其他天作之合一样,这需要双方了解彼此的长处和短处,并尊重对方的界线。我祝双方能 够幸福地生活在一起!

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/9759696/viewspace-88592/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2008-01-03

  • 博文量
    17
  • 访问量
    17746