对于一个能够访问源代码的经验丰富的Java开发人员来说,任何程序都可以被看作是博物馆里透明的模型。类似线程转储(dump)、方法调用跟踪、断点、切面(profiling)统计表等工具可以让我们了解程序目前正在执行什么操作、刚才做了什么操作、未来将做什么操作。但是在产品环境中情况就没有那么明显了,这些工具一般是不能够使用的,或最多只能由受过训练的开发者使用。支持团队和最终用户也需要知道在某个时刻应用程序正在执行什么操作。
为了填补这个空缺,我们已经发明了一些简单的替代品,例如日志文件(典型情况下用于服务器处理)和状态条(用于GUI应用程序)。但是,由于这些工具只能捕捉和报告可用信息的一个很小的子集,并且通常必须把这些信息用容易理解的方式表现出来,所以程序员趋向于把它们明确地编写到应用程序中。而这些代码会缠绕着应用程序的业务逻辑,当开发者试图调试或了解核心功能的时候,他们必须"围绕这些代码工作",而且还要记得功能发生改变后更新这些代码。我们希望实现的真正功能是把状态报告集中在某个位置,把单个状态消息作为元数据(metadata)来管理。
在本文中我将考虑使用嵌入GUI应用程序中的状态条组件的情形。我将介绍多种实现这种状态报告的不同方法,从传统的硬编码习惯开始。随后我会介绍Java 1.5的大量新特性,包括注解(annotation)和运行时字节码重构(instrumentation)。
状态管理器(StatusManager)
我的主要目标是建立一个可以嵌入GUI应用程序的JStatusBar Swing组件。图1显示了一个简单的Jframe中状态条的样式。
图1.我们动态生成的状态条
由于我不希望直接在业务逻辑中引用任何GUI组件,我将建立一个StatusManager(状态管理器)来充当状态更新的入口点。实际的通知会被委托给StatusState对象,因此以后可以扩展它以支持多个并发的线程。图2显示了这种安排。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/java/)图2. StatusManager和JstatusBar
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/java/) 现在我必须编写代码调用StatusManager的方法来报告应用程序的进程。典型情况下,这些方法调用都分散地贯穿于try-finally代码块中,通常每个方法一个调用。
public void connectToDB (String url) {
StatusManager.push("Connecting to database");
try {
...
} finally {
StatusManager.pop();
}
}
这些代码实现了我们所需要功能,但是在代码库中数十次、甚至于数百次地复制这些代码之后,它看起来就有些混乱了。此外,如果我们希望用一些其它的方式访问这些消息该怎么办呢?在本文的后面部分中,我将定义一个用户友好的异常处理程序,它共享了相同的消息。问题是我把状态消息隐藏在方法的实现之中了,而没有把消息放在消息所属的接口中。
面向属性编程
我真正想实现的操作是把对StatusManager的引用都放到代码外面的某个地方,并简单地用我们的消息标记这个方法。接着我可以使用代码生成(code-generation)或运行时反省(introspection)来执行真正的工作。XDoclet项目把这种方法归纳为面向属性编程(Attribute-Oriented Programming),它还提供了一个框架组件,可以把自定义的类似Javadoc的标记转换到源代码之中。
但是,JSR-175包含了这样的内容,Java 1.5为了包含真实代码中的这些属性提供了一种结构化程度更高的格式。这些属性被称为"注解(annotations)",我们可以使用它们为类、方法、字段或变量定义提供元数据。它们必须被显式声明,并提供一组可以包含任意常量值(包括原语、字符串、枚举和类)的名称-值对(name-value pair)。
注解(Annotations)
为了处理状态消息,我希望定义一个包含字符串值的新注解。注解的定义非常类似接口的定义,但是它用@interface关键字代替了interface,并且只支持方法(尽管它们的功能更像字段):
public @interface Status {
String value();
}
与接口类似,我把@interface放入一个叫做Status.java的文件中,并把它导入到任何需要引用它的文件中。
对我们的字段来说,value可能是个奇怪的名称。类似message的名称可能更适合;但是,value对于Java来说具有特殊的意义。它允许我们使用@Status("...")代替@Status(value="...")来定义注解,这明显更加简捷。
我现在可以使用下面的代码定义自己的方法:
@Status("Connecting to database")
public void connectToDB (String url) {
...