更新:Oracle 已确认这是一个错误。
总结:在 JDK 1.6 中工作的某些 custom BeanInfo
s 和PropertyDescriptor
s 在 JDK 1.7 中失败了,有些只有在 Garbage Collection 运行并清除了某些 SoftReferences 后才会失败。
编辑:这也将破坏
ExtendedBeanInfo
Spring 3.1 中的内容,如帖子底部所述。编辑:如果您调用 JavaBeans 规范的第 7.1 或 8.3 节,请准确解释规范的这些部分需要什么。在这些部分中,语言不是强制性的或规范性的。这些部分中的语言是示例的语言,作为规范充其量是模棱两可的。此外,该
BeanInfo
API 专门允许更改默认行为,并且在下面的第二个示例中显然被破坏了。
Java Beans 规范查找返回类型为 void 的默认 setter 方法,但它允许通过java.beans.PropertyDescriptor
. 使用它的最简单方法是指定 getter 和 setter 的名称。
new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");
这在 JDK 1.5 和 JDK 1.6 中有效,即使在其返回类型不是 void 时也可以指定 setter 名称,如下面的测试用例所示:
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import org.testng.annotations.*;
/**
* Shows what has worked up until JDK 1.7.
*/
public class PropertyDescriptorTest
{
private int i;
public int getI() { return i; }
// A setter that my people call "fluent".
public PropertyDescriptorTest setI(final int i) { this.i = i; return this; }
@Test
public void fluentBeans() throws IntrospectionException
{
// This throws an exception only in JDK 1.7.
final PropertyDescriptor pd = new PropertyDescriptor("i",
PropertyDescriptorTest.class, "getI", "setI");
assert pd.getReadMethod() != null;
assert pd.getWriteMethod() != null;
}
}
自定义BeanInfo
s 的示例,它允许PropertyDescriptor
在 Java Beans 规范中对 s 进行编程控制,它们的设置器都使用 void 返回类型,但规范中没有任何内容表明这些示例是规范的,现在这个低级实用程序的行为已经在新的 Java 类中发生了变化,这恰好破坏了我正在处理的一些代码。
JDK 1.6 和 1.7 之间的包中有许多更改java.beans
,但导致此测试失败的更改似乎在以下差异中:
@@ -240,11 +289,16 @@
}
if (writeMethodName == null) {
- writeMethodName = "set" + getBaseName();
+ writeMethodName = Introspector.SET_PREFIX + getBaseName();
}
- writeMethod = Introspector.findMethod(cls, writeMethodName, 1,
- (type == null) ? null : new Class[] { type });
+ Class[] args = (type == null) ? null : new Class[] { type };
+ writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
+ if (writeMethod != null) {
+ if (!writeMethod.getReturnType().equals(void.class)) {
+ writeMethod = null;
+ }
+ }
try {
setWriteMethod(writeMethod);
} catch (IntrospectionException ex) {
现在不再简单地接受具有正确名称和参数的方法,而是PropertyDescriptor
检查返回类型以查看它是否为空,因此不再使用流利的设置器。在这种情况下PropertyDescriptor
抛出一个IntrospectionException
:“找不到方法:setI”。
然而,这个问题比上面的简单测试要隐蔽得多。PropertyDescriptor
在自定义中指定 getter 和 setter 方法的另一种方法BeanInfo
是使用实际Method
对象:
@Test
public void fluentBeansByMethod()
throws IntrospectionException, NoSuchMethodException
{
final Method readMethod = PropertyDescriptorTest.class.getMethod("getI");
final Method writeMethod = PropertyDescriptorTest.class.getMethod("setI",
Integer.TYPE);
final PropertyDescriptor pd = new PropertyDescriptor("i", readMethod,
writeMethod);
assert pd.getReadMethod() != null;
assert pd.getWriteMethod() != null;
}
现在,上面的代码将在 1.6 和 1.7 中通过单元测试,但是由于导致第一个示例立即失败的相同更改,代码将在 JVM 实例生命周期的某个时间点开始失败。在第二个示例中,唯一表明出现任何问题的迹象是在尝试使用 custom 时PropertyDescriptor
。setter 为 null,大多数实用程序代码都认为该属性是只读的。
diff 中的代码在里面PropertyDescriptor.getWriteMethod()
。它在SoftReference
持有实际设置器Method
为空时执行。此代码由第一个示例中的构造函数调用,该PropertyDescriptor
示例采用上面的访问器方法名称,因为最初没有Method
保存在SoftReference
s 中保存实际的 getter 和 setter。
在第二个示例中,read 方法和 write 方法由构造函数存储在SoftReference
对象中PropertyDescriptor
,首先这些将包含对构造函数的getterreadMethod
和writeMethod
setterMethod
的引用。如果在某些时候这些软引用被清除,因为垃圾收集器被允许这样做(它会这样做),那么getWriteMethod()
代码将看到SoftReference
返回 null,它会尝试发现设置器。这一次PropertyDescriptor
,使用内部导致第一个示例在 JDK 1.7 中失败的相同代码路径,它会将写入设置Method
为,null
因为返回类型不是void
。(返回类型不是Java方法签名的一部分。)
在使用自定义时,随着时间的推移,这种行为会发生变化,这BeanInfo
可能会非常令人困惑。尝试复制导致垃圾收集器清除那些特定SoftReferences
的条件也很乏味(尽管可能一些仪器模拟可能会有所帮助。)
SpringExtendedBeanInfo
类具有与上述类似的测试。这是一个实际的 Spring 3.1.1 单元测试ExtendedBeanInfoTest
,它将在单元测试模式下通过,但正在测试的代码将在 GC 后的阴险模式下失败::
@Test
public void nonStandardWriteMethodOnly() throws IntrospectionException {
@SuppressWarnings("unused") class C {
public C setFoo(String foo) { return this; }
}
BeanInfo bi = Introspector.getBeanInfo(C.class);
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
}
一个建议是,我们可以通过防止 setter 方法仅可轻松访问来保持当前代码与非 void setter 一起工作。这似乎可行,但这是对 JDK 1.7 中更改的行为的一种破解。
问:是否有一些明确的规范规定非空位二传手应该被诅咒?我什么也没找到,我目前认为这是 JDK 1.7 库中的一个错误。我错了,为什么?