在Java开发中,配置管理是一个不可或缺的环节。开发者经常面临一个选择:是将配置信息放在注解中,还是XML文件中。这两种方法各有利弊,注解将配置与Java源代码紧密关联,但修改注解需要Java知识并重新编译;而XML文件易于修改,并且可以在运行时重新读取(有时甚至不需要重启应用程序),但它们非常冗长,编辑时容易出错。
通常,最佳做法是结合使用这两种方法。例如,遵循的一条经验法则是:“任何指向Java元素(包、类、方法)的配置参数应该在注解中指定”(例如@Test或@Transaction),而“任何适用于整个应用程序的配置参数可能属于XML文件”(例如输出日志文件的名称或目标Web服务器)。
最近,一种结合这两种技术的方法也受到了关注:能够使用XML文件覆盖Java注解。还没有遇到可以宣称是事实上的标准的实现,目前怀疑这种方法不太可能流行起来,因为它似乎结合了两种方法的最差方面。
考虑以下简单的代码:
public class MyTest {
@Test(invocationCount = 10)
public void verify() {
// ...
}
}
这段代码指示TestNG调用verify()测试方法十次。
如果要在运行时覆盖这个值,发现自己不得不编写相当复杂的XML代码来精确指定想要覆盖的注解。它可能看起来像这样:
<override-annotation>
<package name="org.foo.tests">
<class name="MyClass">
<method name="verify">
<annotation name="org.testng.Test">
<attribute name="invocationCount" value="15"/>
</annotation>
</method>
</class>
</package>
</override-annotation>
当然,可能想要找到一种方法来捕获覆盖(注解/属性部分),以便可以在XML文件的其他位置重复使用它,以防想让这个覆盖应用于不仅仅是一个方法。这可能通过在XML文件顶部定义一个“override-ref”来实现,然后可以在XML文件中多次使用它(就像ant的classpath-ref一样)。
这已经是很多工作了(而且很难阅读),但它也非常脆弱,因为如果决定重命名类或方法名,它很可能会中断(IDE开始将重构扩展到非Java文件,但还没有完全实现)。
由于这些缺点,一直在与TestNG一起研究一种稍微不同的方法,让可以在Java中指定这个运行时覆盖。这并不是万能的,也存在上述表达的一些妥协,但如果能接受修改覆盖时需要重新编译的想法(但不是要覆盖的注解代码,所以这已经是进步了),实现起来其实相当简单。
TestNG引入了以下接口:
public interface IAnnotationTransformer {
void transform(ITest annotation, Class testClass, Constructor testConstructor, Method testMethod);
}
使用方式如下:
public class MyTransformer implements IAnnotationTransformer {
public void transform(ITest annotation, Class testClass, Constructor testConstructor, Method testMethod) {
if ("verify".equals(testMethod.getName())) {
annotation.setInvocationCount(15);
}
}
}
这种方法的想法是,每当TestNG解析@Test注解时,它会在使用它之前通过注解转换器运行它。
方法的三个额外参数让知道这个注解在哪里找到的(在类、构造函数或方法上)。这三个参数中只有一个是非空的。
注意transform()方法的参数是一个ITest。这是因为TestNG支持标准和JavaDoc注解,所以ITest是一个简单的代表@Test(JDK)或@testng.test(JavaDoc)注解的门面对象。在更一般的框架中,参数可能直接是注解的类型,尽管仍然有一些问题需要解决(见下一点)。
这种方法需要对注解有写入访问权限,这在当前的JDK实现中是不可能的。可以通过使用门面来绕过这个限制,这个门面可能作为一个动态代理生成。