在设置Hudson进行持续集成测试(在JeOS服务器上)时,我遇到了一些奇怪的行为,我希望SO的优秀人员可以向我解释。
我们的单元测试在很大程度上依赖于域对象的使用,必须设置许多属性(由于数据库中的空约束)。为了使我们的测试可读,我们创建了一个类InstantiationUtils,它可以实例化一个对象并通过反射设置一系列属性:
public static <T> T newInstance(final Class<T> type, final KeyValuePair<?>... propertyValues) {
return ReflectionUtils.reflectionOperation(new ReflectionOperation<T>() {
@Override
public T perform() throws Exception {
T object = type.newInstance();
for (KeyValuePair<?> propertyValue : propertyValues) {
String propertyName = propertyValue.getKey();
Object value = propertyValue.getValue();
String setterName = "set" + StringUtils.capitalize(propertyName);
ReflectionUtils.invoke(object, setterName, value);
}
return object;
}
});
}
public static void invoke(final Object target, final String methodName, final Object... params) {
List<Class<?>> parameterTypes = ListUtils.map(asList(params), "class");
Class<?> targetClass = target.getClass();
Method method = MethodUtils.getMatchingAccessibleMethod(targetClass, methodName,
parameterTypes.toArray(new Class<?>[] {}));
invoke(target, method, params);
}
public class Foo {
private String foo;
public void setFoo(final String foo) {
this.foo = foo;
}
}
public class Bar extends Foo {
private String bar;
public void setBar(final String bar) {
this.bar = bar;
}
}
不幸的是,编写此代码的人不再适合我们,但据我所知,它没有任何问题。对于Windows也是如此 - 我们在整个单元测试中使用InstantiationUtils而没有任何问题。
然而,Linux是不同的。事实证明,在Linux中,newInstance()方法仅适用于我们想要实例化的类的直接(即未继承)成员。InstantiationUtils.newInstance(Bar.class,“bar”,“12345”);将工作,而InstantiationUtils.newInstance(Bar.class,“foo”,“98765”);将在Linux上失败,但有以下例外:
xxx.xxx.xxx.ReflectionUtils $ ReflectionException:java.lang.NoSuchMethodException:属性'foo'没有setter方法
在Windows上,两个调用都可以工作(我知道newInstance签名不匹配;我们有几个重载的newInstance()方法将参数转换为KeyValuePairs)。
我很难接受继承的公共方法的处理方式不同,所以我已经用我能想到的各种方式对其进行了测试。并且它总是得出结论,在Linux下,至少使用Reflection的上述用法,我们无法访问公共继承的方法。
在Windows上,我使用的是Sun的JRE 1.6.0.11,在Linux中它也是Sun,但是版本是1.6.0.7。
任何人都可以确认这是否正确?或者反射用法是否有些缺陷?
答案 0 :(得分:3)
您正在使用MethodUtils,它有一些limitations:
已知限制
在默认访问超类中访问公共方法
调用默认访问超类中包含的公共方法时会出现问题。 Reflection可以很好地定位这些方法并正确地将它们指定为公共方法。但是,如果调用该方法,则抛出IllegalAccessException。
要检查的另一件事是setFoo()方法是否重载,这也可能导致问题......
答案 1 :(得分:2)
可能是SecurityManager
设置在不同的Java运行时之间有所不同吗?
当然我怀疑这是平台问题 - 这几乎肯定与两个环境之间的JRE版本/设置有关
您确实需要将源代码发布到 MethodUtils.getMatchingAccessibleMethod
答案 2 :(得分:1)
要尝试的几件事......
在Linux上,尝试在不对getFoo()进行反射调用的情况下编写代码 - 如果它不能编译,那么反射就没有希望工作了(这取决于yoiu如何在运行时设置CLASSAPTH ...)
尝试添加以下代码并在Linux和Windows上运行。
final Properties properties;
properties = System.getProperties();
for(final Entry<Object, Object> entry : properties.entrySet())
{
System.out.println(entry.getKey() + " " + entry.getValue());
}
检查输出以确保您使用的是smae JDK / JRE。还要检查以确保类路径正确,以便您实际加载您认为正在加载的内容。
答案 3 :(得分:1)
神秘部分解决了:
MethodUtils.getMatchingAccessibleMethod()显然在Linux和Windows上的工作方式不同。
通过使用MethodUtils.getAccessibleMethod(),它可以工作。为什么,我不知道,但我猜测MethodUtils在弄清楚Method应该有什么签名时会以某种方式误解参数列表。
我想花更多的时间来研究这个问题,但是一如既往要做的事情和要交付的项目,所以我必须接受getAccessibleMethod的工作,然后继续: - )
感谢大家的投入!
答案 4 :(得分:0)
您在Linux,Sun,GCJ等上使用哪种JVM?如果你使用的不是Sun的JVM,你可以尝试安装它,看看是否有所作为。
答案 5 :(得分:0)
您有不同的区域设置吗? StringUtils.capitalize(propertyName)
可能产生不同的输出。
答案 6 :(得分:0)
您检查了CLASSPATH
吗?您是否正在选择要实例化的类的不同版本,具体取决于您所在的平台? (例如旧的代码库等等?)