AOP@Work:使用方面的下几个步骤-学习建议之后

简介:一旦涉足方面之后,您就会马不停蹄,但不带地图去旅行绝对不是个 好 主意。在本文,尊敬的方面发明人 Ron Bodkin 为您提供了成功地采用方面的四 个阶段,从使用跟踪和测试的第一个实验一直到构建自己的可重用方面库。

到目前为止,您一定已经听说过面向方面编程。您知道方面便于进行记录日 志 和测量,而且还可以应用于更复杂的问题。您可能已经下载并编写了一些简单的 方面,或试用了 Spring 框架等使用 AOP 来简化开发的产品。但接下来呢?方 面 还可以为您做什么?

如果您刚开始学习方面并正在疑惑如何用 AOP 进行下一步,那么本文正适合 您。如果您对 AOP 的大轮廓感到兴奋,但不确定如何将其应用于日常开发实践 或 如何说服组织中的决策人采用 AOP,那么读下去吧。

在本文,我提供了用方面进行下一步的实用指南。我介绍了采用 AOP 的不同 阶段,并提供了示例学习应用程序和成功完成每个阶段的指南。在这个过程中, 我提供了对 AOP 技术和应用程序的一份调查,在本系列的其他许多文章中对这 些 技术和应用程序有深入讨论。

采用阶段

在图 1 中,可以看到采用 AOP 的一般步骤。我喜欢基于学习曲线来研究采 用 阶段。在学习任何技能时,尝试适合您的经验的应用程序非常重要。与面向对象 编程一样,AOP 需要投入时间和精力来重塑思考问题的方法。

没有经验的方面用户已经建立了编写方面的常见反模式,即方面在本质上是 修 补程序,但没有内部粘合性。早期的面向对象开发人员在尝试使用对象解决复杂 问题时,创造了类似的反模式(比如深继承层次结构)。让我们避免这些陷阱! 在图 1 中,可以看到成功采用 AOP 的阶段:

图 1. AOP 采用阶段

在采用阶段的整个过程中,要应用下列几点关键原则:

递增采用:学会每次一点点地使用方面。从 “开发方面” 开始,从而避免 让 您的生产系统冒险。然后有效利用它们。最后,以此扩展。在每个阶段,一定要 在已经工作的内容基础上构建并寻找新的机会。

重用然后创建:配置预构建组件是有效利用方面强大功能的好办法,正如它 是 有效利用对象强大功能的好办法一样。随着获得了经验,您会希望定制并最终创 建自己的可重用组件。

投资在惊喜上:在请求同事和上级致力于方面之前,提供方面如何解析棘手 问 题的无成本示例。

自然地,随着您在 AOP 领域更有经验,您将获得所需的技能,从而使用它来 实现更有趣的解决方案并相应地获得更大的益处。这意味着更广泛深入地使用方 面,如图 2 所示:

图 2. 使用深度和广度

在下文中,我将遍历采用 AOP 的四个阶段,并基于技能级别分别讨论。在每 一阶段,我提供了可供学习的示例应用程序。注意,示例代码是使用 AspectJ 5 创建的。

阶段 1. 学习和实验

在这一阶段,主要问题是 “如何使用方面?” 和 “如何使其工作?”;传统 上,这意味着下载 AOP 工具和编写代码,比如简单的跟踪方面。最近,许多有 用 的方面库已可供使用,所以开始学习的好办法是下载一个并使用它。我将从初学 者角度探讨这两个选项。

初学方面

日志记录和跟踪是 AOP 的经典学习练习。它们让您能够用方面做一些有趣有 价值的事情,并让您大致了解后续的高级阶段。许多书籍和文章已经对学习使用 方面进行编程作了介绍,所以我将简要介绍我的示例。我只希望您了解适合该采 用阶段的应用程序类型。

方面是对日志记录和跟踪的传统方法的重大改进。在某些情况下 编写方面来 解决这些普通问题很简单。对于比较复杂的情况,则需要对象和方面之间的更多 合作,这最好在稍后的学习阶段去尝试。跟踪方面可以帮助初学者了解不熟悉的 程序部分如何工作,并在调试间歇出现的问题时获得更深的理解。同样地,清单 1 展示了可以如何使用方面来记录代码中的异常:

清单 1. 使用方面记录异常

public aspect LogModelExceptions {  public pointcut modelCall() :    call(public * com.example.myapp.model..* (..));  public pointcut externalModelCall() :     modelCall() && !within(com.example.myapp.model..*);   after() throwing (Exception e) : externalModelCall() {     logger.error("Error in calling the model", e);  }  private Logger logger = Logger.getLogger("LogModelExceptions");}

每当程序从模型调用中返回一个异常时,该方面就记录错误。本课程的另一 个 变体是记录不使用异常链的旧框架(比如 Struts)的原始异常,这样就可以查 看 失败的底层原因。这种方法对于跟踪 “吞下” 异常(即,捕获异常 却不处理)的代码中的异常也十分有用。可以通过将其编译到您的应用程序中来 “插入” 该方面。为此,可以使用支持不同构建配置的 IDE,比如 Eclipse、Ant 或其他构建工具。还可以在装载时将其织入,或许可以检修运行 时 不断失败的复杂测试用例。

初学 AOP 的开发人员有时应用方面来跟踪代 码性能,如清单 2 所示:

清单 2. 使用方面跟踪性能

aspect TrackDataAccessPerformance { public static long threshold = 100L; // milliseconds /** call to any public method in the Hibernate API */ public pointcut dataAccessCall() :   call(public * org.hibernate..*(..)); Object around () : dataAccessCall() && MyApp.scope() {   long start = System.currentTimeMillis();   Object result = proceed();   long end = System.currentTimeMillis();   long elapsed = end - start;   if (elapsed > threshold) {       trackSlow(elapsed, thisJoinPointStaticPart,        thisEnclosingJoinPointStaticPart);   }   return result; } protected void trackSlow(long elapsed, StaticPart calledJP,  StaticPart callingJP) {    System.out.println("Call from "+callingJP+" to "+calledJP+     " took "+elapsed + "ms"); }}public aspect MyApp {  public pointcut scope() : within(com.example.myapp..*);}

TrackDataAccessPerformance 方面打印出比给定临界值慢的任何 方法调用的时间,以及调用的方法和调用该方法的位置。这个方面在您希望检查 一套集成测试时十分有用。用加载时织入来插入该方面是最自然的方法。

强制执行方面

在现有的(以及新的)程序中强制执行规则和策略时,方 面 很有用。即使在学习方面的早期,这也是个非常好的应用程序,尤其是在测试代 码中使用时。下面看到的是我更改 trackSlow() 的实现时, TrackDataAccessPerformance 方面发生的操作:

 protected void trackSlow(long elapsed, StaticPart calledJP,  StaticPart callingJP) {   throw new AssertionFailedException ("Call to "+calledJP+     " took too long: "+elapsed + "ms"); }

现在,由于强制执行了性能临界要 求,方面导致测试因耗时太长而失败。这样自动强制执行了一种策略,即特定操 作不能耗时太长,这比检查测试运行所产生的日志更可靠!

AspectJ in Action(请参阅 参考资料)包含另一个与此类似的强制执行示例。它包含一个 策 略强制执行方面,查找线程在 Swing 中使用不当的情况。当需要避免能够引起 严 重后果的微小 bug 时,强制执行方面能够真正起到不同凡响的作用。

还 可以尝试使用方面来强制执行不允许从一个包调用另一个包等策略。在下面的代 码段中,我使用 EnforceLayering 方面来阻止程序从数据访问层调用模型代码 :

aspect EnforceLayering {  public pointcut modelCall() :    call(public * com.example.myapp.model..* (..));  public pointcut inDataAccess() :    within (com.example.myapp.persistence..*);  declare warning: modelCall() && inDataAccess():     "Don't call the model from the data Access tier";}

使 用 上述所有方面,可以尝试一下横切思想,同时一网打尽程序中的逻辑问题。研究 了这些简单的入门级方面之后,可以探索更多方法来边学习边试验,为此引入了 库方面。

用库方面学习

预构建的库方面在目前来说相对新颖,但 一些好的集合现在已可使用,其中包括 Spring 框架、Glassbox InspecTor、 JBoss Cache 和 GOF 模式库(请参阅 参考资料 来下载这些库)。当然,许多 Spring 和 JBoss 用户在没有意识到的情况下已经在使用方面了,但您可以学到 更多,特别是,只需再前进一步就能跟上 AOP 领域最新的技术。对于初学者, 尝 试一下下面这个简单的练习:

配置沙盒以使用预构建的方面库。这可能 意 味着创建 AspectJ 构建并将其织入 IDE 中的现有项目是一个相当小的项目。或 者可能意味着设置 Tomcat 5.5 和 Java™ 5 的副本来实现容易的加载时 织 入。不久,它甚至可能意味着下载具有内置 AOP 支持的 VM,比如 BEA JRockIt 最近已经原型化了!(请参阅 参考资料 中 Joe Shoop 的示例沙盒以及有关加 载 时织入支持的信息。)

运行系统并查看预构建方面如何处理跟踪性能、应用安全性或管理事务处理 等 要求。在一些情况下,这意味着连接客户机以查看新数据。在其他情况下,这意 味着编写小集成测试以展示方面按照预期正在与您的代码进行交互。甚至可能编 写另一个方面来跟踪库方面的效果。

根据您的环境配置库。阅读有关如何将方面应用于系统的文档。这可能意味 着 用具体方面扩展抽象方面,使用切点将其应用于系统,或使用声明父项将标记接 口添加到系统中的一些类型。如果总是用注释配置方面,不妨尝试使用 declare @annotation 捕获应该应用方面的位置,而不是在整个应用程序中编写注释。

例如,可以尝试使用 Spring 2.0 配置方面,它构建在 Adrian Colyer 的 “ 用 AspectJ 和 Spring 进行依赖项插入” 中所示的代码之上;在这种情况下, 可以提供 AnnotationBeanConfigurerAspect 所捕获的 @Configurable 注释。 或 者可以仅扩展 AbstractBeanConfigurerAspect 基本方面中的 beanCreation 切 点。我在下一节中还提供了用加载时织入扩展 Glassbox InspecTor 库的比较高 级的示例。

可选地阅读其中一些库代码以理解它们如何运作。可以针对环境尝试一些小 更 改或扩展。研究在您的环境中可以如何用方面进行最好的开发。

阶段 2. 解决实际问题

在本阶段,重点转移至应用方面来解决实际问题,但仍是勉强为之。主要问 题 已经由 “这有用吗?” 上升到 “如何能够真正使用它?”;其他重要的问题 是 “可以将其集成到日常工作中吗?” 和 “如何能说服我的同事也采用它?”。

本阶段的大多数工作需要设计如何有效地将方面集成到您的环境和组织中。 我 首先介绍达到该目的一个策略,然后介绍表现本采用阶段特征的一些方面。

早期集成

学习方面时,可能利用工具来有效地使用它们。当考虑将方面应用到您的正 常 开发过程时,随之产生的一个问题是如何将其与您的全部工具套件有效集成。可 能需要对下列这些工具应用策略:

IDE(使用方面来重构、构建、测试、调试和运行并可视化其效果)

API 文档(Javadocs)

构建和连续集成系统

运行时环境(例如,用于加载时织入)

代码覆盖工具

UML 建模或静态分析工具

内置容器(尤其用于 EJB)

在决定您的工具套件将如何支持 AOP 时,思考两个问题很有帮助:如何支持 方面的 开发,如何支持方面影响的 代码的开发。您将希望为创建方面的团队成 员或项目提供专门工具,以确保那些尚未编写过方面的人理解方面。随着项目经 验的增加,这种技能差异将逐渐消失,这正如您第一次使用对象时,但在早期集 成阶段,这一点非常重要。

非方面开发人员的工具

我将首先介绍后面一种工具,因为它更重要:您希望让不使用方面的开发人 员 能够容易地工作,同时逐渐了解方面。至少,需要为非方面开发人员提供构建、 测试和运行织入代码的方法。通常,这意味着将方面和方面测试添加到构建中, 并添加运行配置以测试和运行 IDE 中的织入代码。对用 方面进行开发的最佳支 持来自使用好的 IDE 插件,比如 AJDT。对于使用 Eclipse 3.0 以上版本或 Rational Application Developer 6.0 以上版本的团队,这也是非方面开发人 员 的自然选择。即使您正在使用功能齐全的 IDE 插件,比如 AJDT,将应用程序的 大多数 Java 项目转换为 AspectJ 项目也可能十分困难。如果不采取其他措施 , 这样做会使得开发 Java 代码更加困难(比如,会拖延增量编译)。

我发现加载时织入是在 IDE 内部运行的非常有用的工具:可以选择由 Ant 构 建的最新版本的 jar,并用它运行测试或整个应用程序。加载时织入在用方面构 建时不需要任何特殊的 IDE 支持,也不需要所有的项目都由 IDE 的编译器织入 。配置测试或应用程序来使用加载时织入运行,通常需要在启动配置中编辑 VM 参数,这些参数可由团队中的其他人共享。在图 3 中,我阐明了如何使用 IntelliJ 和加载时织入运行测试。一些 IDE 插件(特别是 AJDT)添加选项来 简 化加载时织入的配置,但使用这种技术不一定需要这些选项!

为加载时织入配置方面在本系列的以前主题中有详细介绍。通常,可以用织 入 代理设置 VM 或应用服务器,或在应用程序中使用织入 ClassLoader。(请参阅 参考资料 获得有关为加载时织入配置方面的详细信息。)

图 3. 用加载时织入运行 IntelliJ 测试

可视化工具

让不使用方面的开发人员能够容易地可视化 方面在代码中的效果,且不更改 正常开发使用的工具,这非常有价值。这通常意味着看到建议(可能)适用的位 置并静态地看到方面影响类型的方式。通用的选项是生成一个 Javadoc,其中带 有指向方面效果的链接(比如,使用 AspectJ ajdoc 工具)。这就获得一定程 度 的可视化,且不需要更改项目或添加 IDE 插件。AspectJ 5 特性有所增加,现 在 可以从批(Ant)构建生成横切结构信息,并使用单个工具(比如 AspectJ ajbrowser)查看效果。

我非常看好更简单的 IDE 插件的前景,它们能够使用增量 Ant 构建支持来 提 供基本可视化,即使使用的 IDE(或项目类型!)只有少量或完全没有内置 AOP 支持。这样的工具目前还不存在,但是,下一个最佳策略就是教会其他开发人员 如何使用工具来可视化、理解并最终开始编写方面。编写具有相应使用广度的方 面也很重要:使用静态横切 AOP 特性,比如 declare soft、declare parents 或类型间声明来更改类型,对工具支持和没有方面经验的开发人员的理解有相当 大的要求。例如,可以在加载时将建议完全应用于某方法。但是,如果模块在它 定义的类型上调用了类型间声明,则必须用 AspectJFor 编译器构建该模块。随 着项目团队对方面越来越有经验,更加深入广泛地使用方面会获得相当可观的益 处,但当团队仍在学习使用方面时,则必须要谨慎,不要在技术上太冒进。

方面开发人员的工具

为那些对使用方面进行开发感兴趣的团队成员提供好的工具支持也很重要, 这 可以允许他们递增地编译、导航和可视化方面的使用。为此,我建议使用最新版 本的带有 AJDT 插件的 Eclipse。使用好的重用使冗余基础设施最小化也十分重 要(即,将方面添加到现有项目或源代码树,但保持基本的项目结构)。

一般情况下,将需要配置 Ant、Maven、CruiseControl 或 DamageControl 等 工具来添加方面,但这通常都相当简单。通常,可以调用 helper 任务或宏,以 允许插入 AspectJ 编译器中,还可以添加任务以织入单个步骤。为构建添加附 加 测试断言和静态 declare error 约束检查(比如,集成和验收测试)向前跨了 一 大步,通常需要为系统添加一个或两个任务。如果希望工具像识别 Java 编译的 输出一样识别 AspectJ 编译的输出(AspectJ 1.5.1 ant 任务已经改进了日志 文 件输出,极大简化了与 CruiseControl 的集成),配置连续集成系统(比如 CruiseControl)需要一些附加工作。

除此之外,一些工具可以立即使用方面,而其他工具需要额外工作来配置和/ 或补充。例如,代码覆盖工具(比如 Cobertura 或 Emma)工作在字节代码上, 并提供了很好的方面覆盖分析。如果您的团队已经投资在只针对 Java 源代码的 覆盖工具(例如 Clover)上,最好的方法就是禁止该工具在方面代码上的使用 , 而使用可兼容覆盖工具,用方面的覆盖数据进行补充。

检查方面要求

如果要将方面合并到项目中,而许多开发人员都没有使用过方面,则仔细测 试 格外重要。您需要测试方面没有被破坏。特别是,希望确保它们的期望值(比如 类型)保存完好。方面通常会因重构而被破坏,比如当非方面开发人员重命名或 移动系统中的其他类型时。Java 重构工具将不会替换特定于 AspectJ 的代码中 类型的使用,除非运行该代码的开发人员记得搜索扩展名为 *.aj 的文件中类型 的完全限定名。即使他这样做了,如果方面使用具有通配符的类型模式,比如 within(com.foo.service.*Service)(如果将 com.fee.service.Service 移动 到 com.fee.service.api.Service 中,它不会自动更新),方面仍会被破坏。

通过对比,当重命名或移动类型时,使用导入类型(例如,within(Service) )或完全限定名的方面代码被破坏的可能性较小。定期检查 AJDT 等工具生成的 横切结构图以查看重构是否已更改了方面效果的范围,也十分有用。参见图 4 中 该工具工作方式的示例:

图 4. AJDT 中的横切结构比较

如果方面有广泛的效果,则使用其他方面来检查其期望值是否保存完好很有 帮 助。在某些情况下,这可能意味着使用 declare warning 添加静态检查。通常 , 条件越复杂,越需要用其他方面检查集成测试中的约束。例如,假设要使用方面 跟踪 GUI 中的脏对象以便进行重新绘制。可以编写测试方面以记录在任何验收 测 试期间由脏跟踪方面标记的控件名称。在测试结束时,测试方面为要运行的测试 查找包含期望结果的文件。如果存在一个文件,则测试方面将脏对象名称的实际 集合与期望结果作比较,如果两者不匹配,则标记为失败。这种方法允许控制希 望的测试覆盖数量,可以从 0 个到所有测试用例。

管理依赖性

将方面集成到大型多模块系统中时,与工具相关的主要挑战之一就是管理模 块 依赖性。模块 在此指构建单位,比如 Eclipse 项目、Ant 项目和 IntelliJ 模 块。当使用多模块系统开发方面时,需要管理源自方面的新依赖性的创建。 当 一 个模块中的方面织入到另一个模块中定义的类型时,循环构建依赖性很容易发生 。考虑下面这个跟踪系统使用状况的简单计量方面:

aspect MeteringPolicy { private int Metered.usage;  declare parents: Playable implements Metered; after(Metered m) returning:   execution(public * Playable.*(..)) && this (m) {   m.usage++; }}public interface Metered {...}

如果 MeteringPolicy 是不同于 Playable 的模块,则其模块对 Playable 的 模块具有逻辑依赖性。虽然 Playable 的模块对 MeteringPolicy 的模块没有逻 辑依赖性,但需要由其织入(也就是说,它需要与其他模块链接)。通过以逻辑 依赖性的顺序构建模块,然后在稍后的步骤中使用加载时织入或 Ant 中单独的 织 入任务进行织入,一般就可以解决这个问题。在 IDE 中,处理时则比较棘手, 尤 其是当希望能够可视化方面对其他模块的效果时。目前,使用这种跨模块依赖性 来允许可视化的最佳实践是设置附加项目,然后构建一个织入链,如图 5 所示 :

图 5. 配置多模块系统以可视化方面效果

链接源 模块是使用 Eclipse Linked Source Folder 特性的 AJDT 项目。该 配置创建了一个链接,来自原始项目的所有源文件夹将被织入为构建路径中作为 源的链接文件夹。它还在其 aspectpath 中具有依赖对象模块,它包括原始项目 的所有类路径条目。依赖对象模块依赖于原始模块,而非依赖于链接源模块。在 快速的增量开发过程中,甚至可以关闭链接源模块,并在希望进行可视化时,将 其重新打开。

避免依赖性

当然,对这种依赖性的最好解决方案就是完全避免它们。在简单的情况下, 可 以使用局部化方面,即构建在单个模块中且对其他模块没有作用的方面。更一般 的策略是使用抽象方面(只依赖所发布的接口)和具体方面(对于应用它们的模 块是局部的)。这是 AspectJ in Action 中讨论的 Participant Pattern 的示 例(我喜欢为每个模块配置参与,而不是为每个类配置参与)。

可以尝试将系统中的所有方面聚集到一个模块中,但大多数项目将会很快扩 展 而依赖系统中的许多模块。从而就使管理方面模块与所影响模块之间的逻辑依赖 性成为最大的挑战。通过对比,应该选择将方面放在它们的 “自然” 模块中。 在这个过程中,为了避免对非方面开发人员的影响,应考虑为方面(甚至方面的 注释语法)使用 .aj 文件扩展名。多数 Java 开发工具至少在忽略项目树中的 .aj 文件时不会出现任何问题。使用这种策略,可以只将 Eclipse 项目的本质 从 Java 项目(忽略方面)更改为 AspectJ 项目(包括方面)。有时候,将方面分 离到不同的文件夹中更方便一些。如果需要的话,仍值得尝试在现有模块中为支 持方面的代码使用单独的源代码树,而不是为方面创建单独的模块。此外,保持 方面模块分离并让它们具有逻辑依赖性(正如使用任何其他模块一样)有非常好 的理由。指导原则就是最小化模块之间的耦合并最大化模块内部的内聚。

如果您正面临着包含方面的模块之间存在循环逻辑依赖性,则可以应用 Dependency Inversion Principle,正如在面向对象编程中一样:使更抽象的类 型依赖于接口抽象而不是依赖于实现。在上例中,为实现这一点,可以将 Playable 接口移动到单独的模块中,这样 MeteringPolicy 模块可以依赖于该 接 口,而不用依赖于实现 Playable(它织入到)的任何类。

现实项目的方面

我已经简单介绍了将方面有效集成到工作过程中的工具和技术。现在我们来 看 看方面在哪些地方可以真正发挥作用,并促使犹豫不决的团队成员接受它们。使 用方面解决日常问题,比如安全性强制执行、性能监控、高速缓存耗时计算和错 误处理等,是将其集成到项目中的好方法,这样的话,方面可以添加许多功能, 但仍然不会干扰正常的开发。下面的示例省略了一些详细信息,但保留了集成问 题,集成问题是在现实项目中使用方面第二个阶段的特征。

安全性强制执行

方面是强制执行安全规则(比如基于角色的访问控制)的好方法。相关示例 是 强制执行下列要求:用户在使用应用程序时必须具有有效的许可证。检查有效许 可证需要在执行许多操作时不断地强制执行。在许多地方检查有效许可证使得破 坏许可证方案更加困难。这还支持更细粒度的许可证机制,比如为不同的许可证 授予不同的权利。方面非常适合应对这种问题,如清单 3 所示:

清单 3. 用于许可证强制执行的方面

private aspect LicenseEnforcement { pointcut startup() :   execution(void main(String[])); pointcut enforcementPoint() :   execution(private * *(..)) && scope();  pointcut termination() :   startup(); pointcut scope() :    within(com.myapp.*) || within(org.apache..*); before() : startup () {   license = licenseManager.acquire(); } before() : enforcementPoint() {   if (license==null || !license.isValid()) {      throw new IllegalStateException("License violation");   } } after() : termination() {     licenseManager.release(license); } public void setLicenseManager(LicenseManager mgr) { ... }; public LicenseManager getLicenseManager() { ... }; private License license; private LicenseManager licenseManager;}

该方面负责在启动时获取许可证、在许多位置检查有效许可证以及发放许可 证 。在本例中,当在系统中执行范围内的任何私有方法时就会强制执行许可证检查 。该范围包括 Apache 中的一个第三方开放源码库,从而使得在提供的应用程序 中集成强制执行更加容易。您可能在疑惑如何初始化许可证管理器。该方面设计 为通过依赖性注入来配置,如 Adrian Colyer 的 “用 AspectJ 和 Spring 进 行 依赖项插入” 所述。当然,该方面还可以只查询许可证管理器的配置,或者甚 至 直接构造一个配置。

错误处理

方面在改进应用程序响应错误条件的可靠性和一致性上实在太有用了。示例 包 括转换异常类型、处理公共点(比如 Web 应用程序控制器)上的异常、在异常 第 一次发生时进行记录、创建错误汇总报告,以及隔离辅助子系统中的错误使其不 影响核心系统。(我在本系列的上一篇文章中描述了最后两个示例;请参阅 参 考 资料。)

接下来的步骤展示如何为 Web 应用程序创建简单但全面的端到端异常处理策 略。我将从业务模型抛出的异常转换为未检查的 ModelException 的实例。 ModelException 保存有关执行对象和参数的附加上下文信息,而不仅是堆栈跟 踪 。我将展示如何仅一次就完全记录异常和如何处理错误,其中可以通过将用户定 向到正确的信息(其中包括相关数据)以改进和加快用户问题的解决。最好的地 方是,该策略不需要附加到模式密集的框架中:它使用自定义应用程序代码或您 喜爱的第三方库代码。

端到端异常处理

第一个方面,如清单 4 所示,负责将数据访问和服务异常从模型(业务逻辑 )层转换为有意义的异常,并负责捕获有关当前代码正在做什么的上下文信息:

清单 4. 转换模型内的异常

/** Error handling for methods within the model */aspect ModelErrorConversion { /** execution of any method in the model */ pointcut modelExec() :   execution(* model..*(..)); // convert exception types after() throwing (HibernateException e): modelExec() {   convertException(e, thisJoinPoint); }  after() throwing (ServiceException e): modelExec() {    convertException(e, thisJoinPoint); } after() throwing (SOAPException e): modelExec() {   convertException(e, thisJoinPoint); } after() throwing (SOAPFaultException e): modelExec() {   convertException(e, thisJoinPoint); }  // soften the checked exceptions declare soft: ServiceException: modelExec(); declare soft: SOAPException: modelExec(); /** Converts exceptions to model exceptions, sToring context */  private void convertException(Exception e, JoinPoint jp) {    ModelException me = new ModelException(e);   me.setCurrent (jp.getThis());   me.setArgs(jp.getArgs());   // ModelException extends RuntimeException, so this is unchecked     throw me; }}

ModelErrorConversion 方面确保当退出模型包中任何类的方法时, Hibernate 持久化引擎抛出的异常或来自 JAX RPC 调用的异常转换为特定类型的运行时异 常 。它还确保 SOAP 异常被软化:即从必须在 Java 代码中声明或捕获的已检查异 常转换为不需要这样做的运行时异常。该错误处理逻辑还捕获异常的上下文信息 :正在执行的对象和传递给方法的参数。我基于此构建了另一个对象,在从外部 调用模型时执行错误处理,如清单 5 所示:

清单 5. 进入模型时处理异常

/** Error handling for calls from outside into the model */aspect ModelErrorHandling { /** Entries to the model: calls from outside */ pointcut modelEntry() :   call(* model..* (..)) && !within(model..*); /**  * Record information about requests that entered the model from outside  * where an object called them  **/ after(Object caller) throwing (ModelException e) :  modelEntry() && this(caller) {   handleModelError(e, caller, thisJoinPointStaticPart); } /**  * Record information about requests that entered the model from outside  * where no object was in context when they were called, e.g., in a  * static method  */ after() throwing (ModelException e) :  modelEntry() && !this(*) {    handleModelError(e, null, thisJoinPointStaticPart); }  private void handleModelError(ModelException e, Object caller, StaticPart staticPart) {   if (e.getInitialMethod() == null) {     e.setInitialCaller(caller);      e.setInitialMethod(staticPart.getSignature().getName());   }  }}

在该层,我记录了异常的一段额外上下文,也就是在进入模型时正在执行的 对 象。然后可以在 Web 层使用该信息为用户提供帮助信息,如清单 6 所示:

清单 6. 用于用户界面错误处理的方面

aspect UIErrorHandling { pointcut actionMethod (Actionmapping mapping,  HttpServletRequest request) :   execution (ActionForward Action.execute(..)) &&   args(mapping, *, request, *) && within(com.example..*); ActionForward around(Actionmapping mapping,  HttpServletRequest request) :  actionMethod(mapping, request) {   try {      return proceed(mapping, request);   } catch (NoRemoveInUseException e) {     addError("error.noremoveinuse", e.getCurrent());       return mapping.getInputForward();   } catch (ModelException e) {     return handleError(e, mapping, request);   }   catch (InvocationTargetException e) {     ApplicationException ae =       new ApplicationException("populating form. ", e);     return handleError(ae, mapping, request);   }   catch (Throwable t) {     ApplicationException ae =       new ApplicationException("unexpected throwable ", t);     return handleError(ae, mapping, request);   } } private ActionForward handleError(ApplicationException ae,   Actionmapping mapping, HttpServletRequest request) {   // track information to correlate with user request   ae.setRequest(request);    ae.logError(getLogger()); // logs context information...    session.setAttribute(Constants.MESSAGE_TEXT,     translate (e.getMessage()));   session.setAttribute(Constants.EXCEPTION, e);   return mapping.findForward(Constants.ERROR_PAGE); }  private void addError(String errorId, Object current) {     // get name and key from Entity objects   // then add a Struts action error }  }

UIErrorHandling 方面负责确保 com.example 包或其子包中的任何 Struts 动作均记录错误并进行处理。对于可恢复的错误(由 NoRemoveInUseException 表示),它只将错误及其相关上下文数据(正在使用的对象)提供给用户,并返 回至同一页面。对于其他错误,它将其转换为应用程序异常(其模型异常是子类 型),并指派给通用错误处理逻辑。错误处理逻辑记录 Web 请求的相关信息, 以 使错误与用户和会话相关联。最后它重定向至错误页面,将遇到的异常存储为附 加属性。

转发页面中存储的属性允许添加带信息的 HTML 备注,支持人员可以使用该 备注将用户错误与已记录的异常相关联。注意,应用程序异常被特殊记录: logError() 方法记录被捕获的所有附加上下文信息,其中提供了信息的 “黑盒 记录” 以确保可以隔离、重新生成和修复错误,而无需您思考 NullPointerException 中什么可能为空。特别是,它记录所有相关信息,比如 调用树中记录的参数和关键对象,以及传统的堆栈跟踪。

扩展库方面

最后,让我们看一下当希望基于现有库方面进行构建时会发生什么。在这种 情况下,我扩展了 Glassbox InspecTor 性能监控库以测量应用程序中 XML 处 理的性能。(请参阅 参考资料 中有关 Glassbox InspecTor 的详细信息。)

可以分六个步骤完成该操作:

1.阅读 Glassbox InspecTor 文档学习如何将其扩展。

2.创建示例 IDE 项目。

3.编写一个简单方面以跟踪 XML 处理代码的性能(如清单 7 所示) 。

4.创建加载时织入描述符。

5.从方面和描述符构建 JAR。

6.用加载 时织入将 JAR 添加到系统中。

步骤 3 如下所示。在这种情况下,应用程序 具有一个 DocumentProcessor 接口,我希望跟踪该接口中定义的任何公共方法 的性能,以确定它们是否缓慢。

清单 7. 扩展 Glassbox InspecTor 以监控自定义代码

public aspect DocumentProcessorMoniTor extends AbstractResourceMoniTor {/** apply template pointcut to moniTor document processor methods */protected pointcut methodSignatureResourceReq() : execution(public * DocumentProcessor.*(..));}

该代码钩住现有 Glassbox InspecTor 框架。它定义了模板切点 methodSignatureResourceReq 以挑选代码中要监控的连接点,也就是 DocumentProcessor 接口上定义的任何公共方法的执行。这将匹配任何接口实现 中的方法,但不匹配这些类中定义的其他方法(在此例中我不希望监控它们)。 然后,框架使用该连接点上的方法名单独分组每个方法的性能统计数据。编写了 这个小方面之后,可以构建一个简单的加载时织入部署描述符 META- INF/aop.xml,如下所示:

Tor.DocumentProcessorMoniTor "/>

然后可以用已编译的类文件将描述符打包到单独的扩展 jar 中,该 jar 将 添加到标准 Glassbox InspecTor 监控能力中。然后部署该 jar 以及标准 Glassbox InspecTor JAR,我可以从新监控器看到我的数据,并用 JMX 看到标 准 Glassbox 监控数据。完成了!AspectJ 5 加载时织入系统和 Glassbox 架构 使得添加新监控器非常简单,根本不需要重新包装或复杂的重新配置。

阶段 3. 将方面集成到核心开发中

在该学习阶段,有一种自然的动力促进更高级的方面使用。上一阶段侧重于 有效的技术集成,而这一阶段将重点转移到更有效的方面使用。这与您第一次开 始用对象进行思考时那种啊哈! 的经历一样。对于那些仍对方面陌生 的人们来说,这里的讨论会有一些复杂,正如对于那些刚开始编写对象的人来说 ,阅读 Gang of Four 令人无法招架一样。

本阶段的特点是方面和类之间的更紧密合作。关键问题是如何暴露所谓的业 务方面的域概念。前期学习阶段编写的方面侧重于可从系统拔出的辅助关注点和 跨许多域应用的水平关注点(比如事务处理管理、高速缓存或跟踪)。本阶段的 另一个主要问题是如何将方面有效集成到完整开发生命周期中,但该主题超出了 本文的范围。

在本阶段,AOP 技术通常在团队和组织内部扩张。正如在阶段 2 的讨论中所 述,组织总是在开始时拥有少数几个方面专家,他们进行大多数方面编码,主要 负责将方面与其他组件集成,并管理方面对其他项目和开发人员的影响。但是, 随着 AOP 的名气在组织内部增长,其他开发人员自然会加深了解并开始编写自 己的方面。

我无法为集成阶段提供详细的示例应用程序,因为要求和设计总是需要向上 扩展,从而值得写一篇专门的文章。相反,我将讨论三个现实的方面实现,它们 是这个阶段的典型体验:细粒度安全性,管理持久对象关系,实现用户界面宏。

细粒度授权

正如上一节所述,基本安全性规则强制执行是方面的自然使用。而且,许多 应用程序必须强制执行更高级的数据级安全性,因此对对象实例甚至其字段的访 问都由规则控制。示例包括 “只有员工的经理或该组的 HR 管理员可以查看敏 感的个人数据” 或 “只有分配给组的经理可以代表组进行交易”。考虑强制执 行第一条规则的示例,如图 6 和清单 8 所示:

图 6. 示例人力资源域模型

清单 8. 细粒度安全性方 面

public aspect SensitiveDataAuthorization {/** * Matches sensitive read operations on employee data, namely reading* sensitive fields*/pointcut readSensitiveEmployeeData(Employee employee):target(employee) && (get(* salary) || get(* address) || get(* bonus));/** * Matches the set up of a security context, in this case for JAAS*/pointcut securityContext(Subject subject, Action action):cflow(execution(* Subject.doAs*(Subject, Action, ..)) &&args(subject, action, ..));before(Subject subject, Employee employee) :readSensitiveEmployeeData(employee) && securityContext(subject, *) {policy.checkAccess(subject, employee);}/** * Matches sensitive read operations on regulation data, namely calling* get methods or calculating tax on it.*/pointcut sensitiveRegOp(EmployeeRegulation reg):this(reg) && (execution(* get*()) || execution(* calcTax()));before(Subject subject, EmployeeRegulation reg) :sensitiveRegOp(reg) && securityContext(subject, *) {Employee employee = reg.getEmployee();policy.checkAccess(subject, employee);}public void setPolicy(EmployeeAccessPolicy policy) { ... }public EmployeeAccessPolicy getPolicy() { ... }protected EmployeeAccessPolicy policy;}public class EmployeeAccessPolicyImp implements EmployeeAccessPolicy {/** * Checks for valid Access to data.* @throws SecurityException if not valid*/public void checkAccess(Employee employee, Worker worker) throws SecurityException {Employee caller = Employee.getEmployee(worker.getSubject());if (caller==null || !employee.reportsTo(caller))) {// record attempted security violationthrow new SecurityException("...");}// log successful data Access to audit trail}}

在清单 8 中,SensitiveDataAuthorization 方面在 readSensitiveEmplData() 上有第一个建议,保护对员工类上直接定义的敏感数 据字段 的读取。每当在安全上下文(在这种情况下,是当用户通过 JAAS 身份 验证时)中进行时,该建议就建议此类读取。该建议指派给 helper 类 EmployeeAccessPolicy 中的 checkAccess() 方法,该方法检查执行主体(通常 是用户)和其数据被访问的员工之间的关系。如果所需关系不存在,则该方法抛 出异常并发出警报。否则,该方法记录尝试的访问。

方面的第二部分开始变得更有趣:在此,我保护了对由组合对象保存的雇员 数据的访问。具体地说,EmployeeRegulation 对象保存敏感数据,比如政府税 务标识符、税率和扣缴信息。自然,该信息随雇员所在的辖区(国家、州、省、 市等)而变。执行计算税方法或从任何规章数据的实现中获取任何数据都被认为 是敏感的。在这种情况下,我需要从规章数据映射回聚合对象(雇员)来检查访 问。

有效的政策强制执行

通过授权示例,开始意识到什么使得方面在政策强制执行中是非常有效的: 连接点的使用是挑选和提炼安全性规则应用条件的强大方法。在本例中,我同时 使用了字段访问和方法调用来应用安全性。还要注意的是,我定义了基于上下文 数据(要访问的主体和员工数据)允许何种访问的业务规则。这比对每个对象实 施低级访问控制可取多了:虽然后者十分灵活,但它实际上使得无法强制执行政 策,而且通常还有配置错误的风险。使用 AOP 的灵活性,很容易在正确的位置 强制执行安全性,并暴露正确的数据以基于业务规则实现自动政策决策。

授权示例还引入了将业务关系映射到方面代码的重要主题。我已经确定了与 给定雇员规章对象相关的雇员。在这种情况下,假设从规章数据到聚合雇员对象 存在逆关联。我还显式地将该信息编码到方面中。在更通用的安全性方面(和其 他业务方面)中,这成为更复杂的问题。

最后要注意的是,安全性对象可以被看作业务政策强制执行的特殊情况。在 使用方面来强制执行规则 “不可以在暂停的帐户中执行任何业务交易” 时,安 全性对象是自然的一步。这意味着需要跟踪可能生成帐户费用的服务。这与强制 执行访问控制规则相似,但它是基于动态上下文的(帐户是否被暂停)。

持久对象关系

在 Aspect-Oriented Software Development 2005 会议上,Nicholas Lesiecki 作了一份行业报告,介绍他和他的同事在 VMS 如何使用方面来让 Hibernate 持久化引擎自动管理对象关系。Nick 是经验丰富的方面开发人员, 他的报告阐明了将方面集成到现有项目的三个典型阶段。

假设有一个域模型,其中经理可以管理 0 到多个员工,每个员工具有指向其 经理的引用。如果希望使用严格的面向对象代码添加员工,则需要编写如下代码 :

employee.setManager(manager);manager.getEmployees().add(employee);

使用持久关系管理方面,可以将代码简化为:

manager.getEmployees().add(employee);

还可以用另一种方式编写:

employee.setManager(manager);

在本例中,方面用于自动传播对象关系中的更改,确保一对多和多对多的双 向关系保持一致。这样,VMS 团队就避免了事务处理失败的风险和进行多余 API 调用的麻烦。

实现该功能要求 VMS 团队紧密集成现有库以通过方面提供附加功能。项目成 员还必须跟踪对象之间的业务关系,他们使用在 Hibernate 中暴露的有关持久 关系的信息来实现这一点。在细粒度安全性示例中,也提出了跟踪方面业务对象 之间的关系这一相同要求。我在下面的业务方面中详细介绍了这一常见要求。

UI 监控和宏

我一直致力于客户项目,在这种项目中,我们使用方面监控富客户机 GUI 应 用程序中的用户行为并记录宏。监控 GUI 应用程序有四个主要目的:

更好地理解用户如何使用应用程序(以便改进使用体验)

对目标用户进行 困难概念或功能的培训

了解不同的用户群

对特性按优先级排序以便将来开 发

我们的方面必须跟踪不同的用户界面事件,并对控件如何结构化以及页面 导航如何运作有所了解。它们还必须能够跟踪应用程序中的状态,标识向导或模 式对话框的调用屏幕,并正确捕获反导航事件(后退按钮)。此外,应用程序监 控相关的系统事件,比如外部连接和用户碰到的任何错误。在捕获到用户交互和 系统事件时,就将其匿名记录在日志文件中,以便进行后续的批传输。

这种方面用法与在 Web 应用程序中捕获用于服务器端分析的数据紧密相关。 由于 Web 请求的常规结构,基本的 URL 分析可用于任何 Web 应用程序。与跟 踪简单的 Web 站点不同,这种监控需要与应用程序的特定 UI 结构集成,以标 识什么事件有意义以及什么视图值得跟踪。在业务流中使用更复杂的请求和变体 ,可以使得应用方面来监控调用后端服务的基于 Web 的、Ajax 或富客户机应用 程序中的用户行为更有用。

使用方面进行 UI 监控是我与 Jason Furlong 撰写的即将面世的行业报告的 主题。我将在 2006 年 3 月召开的 Aspect-Oriented Software Development 会议(请参阅 参考资料)上发表此报告。

我们已经将方面成功用于 UI 监控 ,接下来还将在该应用程序中实现宏记录和回放。这些方面记录鼠标或键盘动作 的 UI 事件,并标识目标 UI 控件。然后记录这些事件以及接收的 UI 视图。在 回放时,当当前目标主窗口(屏幕)和控件可见时,方面会重新生成 UI 事件。 有趣的是,UI 监控实现提供了宏方面跟踪当前屏幕以记录和回放所需的关键逻 辑。

核心集成技术

正如本节中的示例所示,开发的更高级集成阶段带来了新的技术:

暴露业务关系

特定于域的方面通常涉及域中对象和业务概念之间的关系。该信息不在 Java 类、类型和方法的 “信息集合” 中。在一些情况下,注释提供该信息。我认为 理想方法是使用描述域概念的注释(例如 @BusinessTransaction 或 @Aggregates(1, MANY)),并使用这些注释推导出特定业务关注点(比如持久化 规则)。但是,由于标准 Java EE 1.5 注释(尤其是来自 EJB 3.0 Java Persistence API 的那些注释)的出现,许多项目将提供这些注释,从而使得使 用 declare @annotation 和/或切点从注释中标识业务关系更具吸引力(例如, 从级联持久化数据确定对象聚合)。

如果项目不使用注释来跟踪对象关系,则持久化引擎中定义的元数据通常是 项目显式定义业务关系(比如关系聚合和基数)的惟一位置。这使得使用 VMS 这样的技术更具吸引力,VMS 用于从引擎的映射数据确定对象关系。因此,我正 在帮助 Sergei Kojarski 创建一个从 Hibernate 暴露业务关系供方面使用的库 。John Heintz 还致力于使 VMS 代码可重用于 Codehaus Ajlib 方面库项目。 我相信这些发展将使得编写业务方面更加容易。

跟踪连接点序列

在许多大型方面应用程序中,必须跟踪一系列连接点以确定操作所处的状态 并收集上下文数据。这通常是以下案例的扩展,在这种情况下控制流(cflow) 切点十分有用:当不能让前一连接点仍然位于未完成连接点的 “堆栈” 中时, 通常必须存储状态。可以在 percflow 方面中(即,在堆栈中两个连接点的公共 祖先的控制流中)或使用 ThreadLocal 变量来实现这一点。

方面配置

当扩展方面的功能且方面与其他类型交互时,配置和测试就成为一个问题了 。有关方面配置和测试的详细信息,请参阅 Adrian Colyer 的 “用 AspectJ 和 Spring 进行依赖项插入” 和 Nicholas Lesiecki 的 “对方面进行单元测 试”。

方面中的方面

当方面执行应用程序中的多个角色时,让它们显式合作就更理所当然了,比 如使用方面来配置依赖性注入的其他方面。在我的文章 “用 AspectJ 进行性能 监视” 中,提供了两个示例:一个方面隔离其他方面中的错误,一个方面自动 用 JMX 注册对象,其中包括其他方面。

阶段 4. 与他人共享

在用方面进行开发的最后一个阶段,其重点转移到构建泛化、可重用的机制 。正如使用对象一样,它采用了使用技术(AOP)的一般经验以及域经验来编写 好的、可重用的方面代码。Biggerstaff 和 Richter 的 Rule of Three(意味 着设计器应该在创建可重用实现之前检查三个系统)在方面上的应用与在对象上 的应用一样多。

请参阅 Nicholas Lesiecki 的 “用 AspectJ 增强设计模式, 第 2 部分” ,获得有关使用方面和已重构的 Library Observer 模式来跟踪服务使用状况的 详细信息。

提供特性变体是方面在这个复杂阶段的用例。例如,考虑一个商 业软件供应商,它主要提供基于开放源码构建的产品线,并添加了增值业务支持 。行业报告 “Large-scale AOSD for middleware” 描述了 IBM 在该领域的动 机和经验(请参阅 参考资料)。

对于更小更具体的示例,不妨考虑一下使用计量来跟踪客户开票服务的使用 状况。

切点接口

首先介绍不使用面向对象的 Observer 模式时如何实现该要求。通过发布切 点供外部客户机使用就可以做到这一点,例如,方面仅定义了一个切点,供被计 量事件(比如使用标题)发生时使用:

aspect MeteringPolicy {declare parents: Playable implements Metered;public pointcut meteredUse(Metered metered) :titleUse(metered);pointcut titleUse(Metered metered) :this(metered) && ( execution(public void Playable+.play()) ||execution(public void Song.showLyrics()) );}

然后,代码的另一部分可以写入方面,以便在被计量的使用情况(比如,检 查信用、记录费用、立即开票、减少预付款计数器或启动定时器)发生时随时响 应。与传统的 Observer 实现不同,这里无需显式地分配事件。通常,发布切点 比传统的可扩展性机制更灵活,传统的可扩展性机制需要预定义的钩子方法,以 及用通常很笨重的方式来注册和分配给回调。以同样的方式,思考一下典型的面 向对象持久框架提供的事件前和事件后回调类型。例如,EJB 3.0 Java Persistence API 公共草案规范使用注释和回调方法支持下列七个生命周期事件 (每个事件都有两个版本),以便回调持久化的对象或一般监听器:

PrePersist

PostPersist

PreRemove

PostRemove

PreUpdate

Pos tUpdate

PostLoad

使用 AOP 实现,它们可以发布为四个可以随意组合和修 饰的切点。注意,在某些情况下,发布切点供外部使用需要以特殊方式对代码进 行结构化,从而暴露简单切点定义以表示实现中的一系列连接点。

特性变体

我认为计量示例最有趣的一面就是,针对不同上下文改变特性的能力。考虑 可能起作用的不同规则:

使用前检查信用

实时开票

批开票

使用前计量

每会话计量

跟踪 奖励

按用户计量使用情况

按付款来源计量使用情况(比如,匿名的储值卡 )

启用不同的营销活动

报价

启用不同联营机构的能力和跟踪

能够以 模块化方式改变配置计量的方式、应用计量的点、计量后的结果(包括开票以及 可能的暂停和/或拒绝访问)。基本上,可以将计量系统和相关能力打包为一个 随意 选项集合,以根据需要针对特定部署进行组合。在这种情况下,在单个面 向对象代码库中管理所有这些变体将非常困难。虽然面向对象编程允许模块化的 实现,但这需要巨大的模式密度,这会妨碍将来的扩展和改进,而且会越来越趋 向紧耦合。

编写可重用的方面

在多数情况下,构建好的方面库需要与构建好的库同样的技能和知识。特别 是,它需要域经验、好品味、技术(方面)经验,通常还需要系统编程知识(例 如,为了避免内存泄漏或支持线程安全的高并发度)。但是,比这些更重要的是 ,需要掌握一些新技能才能构建好的方面库:

一些以前的 AOP@Work 文章深入讨论过可重用的方面,特别是

“用 AspectJ 增强设计模式”

“用 AspectJ 进行性能监视”

“用 AspectJ 和 Spring 进行依赖项插入”

同样地,“使用 AspectJ 5 检验库方面” 讨论了编写可重用方面库时碰到 的问题。

可扩展性设计

首先,必须仔细计划将如何对方面库进行配置、扩展、部署和限制(可能的 话)。我在 “AspectJ 进行性能监视,第 2 部分” 中详细介绍了方面库可扩 展性设计的一些策略。

完整性设计

我曾亲身体会到确保可重用方面处理与其交互的模块之间的所有 交互是多么 重要。特别是,自然要编写方面来扩展或利用其他库的功能,比如监控性能、管 理对象之间的持久关系或跟踪库中的错误。为其他库执行这些任务的可重用库需 要跟踪为该库使用的所有 有效模式,而不仅仅是特定开发人员知道的那些模式 。简而言之,它要求必须真正掌握要扩展的库的设计,而不是仅仅知道使用该库 的一种方法。

在今天的许多案例中,使用方面来扩展另一个库是在对发生在系统生命周期 中的事件存在相当不一致的或无文档记录的定义的情况下进行的。必须确保实际 系统状态符合所有有效用户的期望(尤其是跨系统的可移植性),并将所有 API 机制考虑进来以实现给定目标。相比之下,当构建面向对象库来扩展一些现有库 时,通常只需要寻找一种有效利用功能的标准方法。理解该规则的另一种方法是 ,方面提供了一致地处理事情的强大功能,所以一定要全面理解方面正在进行的 操作及其操作环境。同样地,可重用方面应具有清晰的版本化策略和对合作库版 本的要求(正如类库一样)。

整个生命周期的计划

仔细注意系统生命周期中方面的交互。初始化总是对象生命周期中棘手的时 刻。初始化方面时,必须仔细考虑与静态(类)和实例(对象)初始化以及其他 方面的交互,其中包括如何正确地处理错误。方面初始化需要在方面被初始化的 任何时候都是正确的。此外,AspectJ 的默认单体方面在其类被加载时立即初始 化,从而使得在加载已配置方面类型之前必须启动任何配置机制。我已经体验过 此规则的重要性,尤其是在编写方面来处理日志记录、错误处理、运行时控制、 配置以及自动注册 JMX 类型时。

编写瘦方面

使方面和与其所合作的代码之间的隔离性最大化非常重要。在许多方面,这 只是通过使依赖性最小化、宽容对待您所接受的值并严格对待您所提供的值来避 免耦合的旧规则。编写指派给实现对象的 “瘦” 方面是我发现的极有价值的技 术。当用依赖性注入配置方面并仅依赖接口时,这一点非常有用,它可以使得更 改实现更加容易。

学习以不同方式进行重用

AOP 允许一种新的可重用代码。它允许随意 使用具有特性变体和开放可扩展 性的低耦合功能。AOP 库通常包括方面、类和接口之间的合作。我第一次为 AOP 感到兴奋是因为通过在递增式采用中避免显著的耦合、巨大的前期投资和困难, 获得了改进传统面向对象框架的机会。我相信,重用将被证明是通过采用 AOP 获得的最大益处。

结束语

在对成功采用方面的四个阶段的介绍中,我提供了对方面采用过程的概述。 除了在每个阶段为开发人员和组织布置核心关注点之外,我还讨论了在不同成熟 阶段可以有效尝试并使用的特定种类的方面。有用的方面与有用的对象一样多, 所以我鼓励您思考对您的系统有用的那些方面。

通过这些讨论,我构建了成功使用方面的四个基本原则,在每个采用阶段都 会看到这些原则:

递增式采用:在每一阶段,将方面应用于可实现和有价值的地方。

重用然后创建:努力寻求可重用或可扩展的现有解决方案,而不要总是发明 新的解决方案。

投资在惊喜上:确保同事对于使用方面从始至终都保持好印象。

学习理论和实践(不断提高技能):平衡动手实践和阅读一些可用的优秀参 考资料,其中包括 参考资料 中突出显示的那些资料。

如果要继续使用方面 ,必须递增地了解其价值,正确地将其演示给组织中的其他人,然后在其他开发 人员开始同一过程时予以帮助。我希望本文能够给予您一些在任一成熟阶段对方 面进行有效使用的好主意,以及进入下一阶段的路标。本文讨论的许多应用程序 和原则在 AOP@Work 系列的其他文章中都有深入介绍,请参阅 参考资料 进一步 学习。我希望听到您的体验!

以后我会去到很多很繁华或苍凉,

AOP@Work:使用方面的下几个步骤-学习建议之后

相关文章:

你感兴趣的文章:

标签云: