为什么PropertyDescriptor行为从Java 1.6更改为1.7?
更新:Oracle已确认这是一个bug 摘要:在JDK1.6中工作的某些自定义为什么PropertyDescriptor行为从Java 1.6更改为1.7?,java,spring,javabeans,java-7,Java,Spring,Javabeans,Java 7,更新:Oracle已确认这是一个bug 摘要:在JDK1.6中工作的某些自定义BeanInfos和PropertyDescriptors在JDK1.7中失败,一些只有在垃圾收集运行并清除某些软引用后才会失败 编辑:这也将打破Spring3.1中的扩展Beaninfo,如文章底部所述 编辑:如果调用JavaBeans规范的7.1或8.3节,请解释 规范中那些部分需要什么的地方。这个 在这些章节中,语言不是强制性的或规范性的。这个 这些章节中的语言都是示例,这些示例充其量是 模棱两可的规范。此外,B
BeanInfo
s和PropertyDescriptor
s在JDK1.7中失败,一些只有在垃圾收集运行并清除某些软引用后才会失败
编辑:这也将打破Spring3.1中的扩展Beaninfo
,如文章底部所述
编辑:如果调用JavaBeans规范的7.1或8.3节,请解释
规范中那些部分需要什么的地方。这个
在这些章节中,语言不是强制性的或规范性的。这个
这些章节中的语言都是示例,这些示例充其量是
模棱两可的规范。此外,BeanInfo
API
特别允许更改默认行为,并且
在下面的第二个例子中,这显然是不正确的
JavaBeans规范寻找具有void返回类型的默认setter方法,但它允许通过Java.Beans.PropertyDescriptor
自定义getter和setter方法。使用它的最简单方法是指定getter和setter的名称
new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");
这在JDK 1.5和JDK 1.6中起到了作用,可以指定setter名称,即使其返回类型不是void,如下面的测试用例所示:
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的示例允许对Java Beans规范中的PropertyDescriptor
s进行编程控制,它们的setter都使用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
不再简单地接受具有正确名称和参数的方法,而是检查返回类型是否为null,因此不再使用fluent setter。在本例中,PropertyDescriptor
抛出一个IntrospectionException
,“未找到方法:setI”
然而,这个问题比上面的简单测试更加隐蔽。在自定义BeanInfo
的PropertyDescriptor
中指定getter和setter方法的另一种方法是使用实际的方法
对象:
@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实例生命周期的某个时间点,由于导致第一个示例立即失败的相同更改,代码将开始失败。在第二个示例中,尝试使用自定义PropertyDescriptor
时,唯一的错误指示出现。setter为null,大多数实用程序代码将其视为只读属性
diff中的代码位于PropertyDescriptor.getWriteMethod()中。当持有实际setter方法的SoftReference
为空时执行。在第一个示例中,PropertyDescriptor
构造函数调用此代码,该示例采用上述访问器方法名称,因为最初在SoftReference
中没有保存实际getter和setter的方法
new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");
在第二个示例中,读方法和写方法由构造函数存储在PropertyDescriptor
中的SoftReference
对象中,首先它们将包含对readMethod
和writeMethod
方法的引用。如果在某个时刻,这些软引用被清除,就像垃圾收集器被允许做的那样(它也会这么做),那么getWriteMethod()
代码将看到SoftReference
返回null,它将尝试发现setter。这一次,使用导致JDK 1.7中第一个示例失败的PropertyDescriptor
中的相同代码路径,它将把write方法设置为null
,因为返回类型不是void
。(返回类型不是Java语言的一部分。)
在使用自定义BeanInfo
时,随着时间的推移,行为会发生这样的变化,这可能会让人非常困惑。尝试复制导致垃圾收集器清除那些特定的SoftReferences
的条件也是乏味的(尽管一些检测模拟可能会有所帮助)
SpringExtendedBeanInfo
类的测试与上面的测试类似。下面是来自extendedbeanninfotest
的实际Spring 3.1.1单元测试,它将在单元测试模式下通过,但被测试的代码将在后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一起工作。这似乎是可行的,但这是对JDK1.7中改变的行为的一种攻击
问:是否有明确的规范规定非无效设置者应该被诅咒?我找不到任何东西,目前我认为这是JDK 1.7库中的一个bug。
我错了吗?为什么?看起来规范没有改变(它需要void setter),但是实现已经更新为只允许void setter
规格:
具体请参见第7.1节(访问器方法)和第8.3节(简单属性的设计模式)
请参阅此问题后面的部分答案:
第8.2节规定:
然而,在JavaBean中,使用与设计模式匹配的方法和类型名是完全可选的。如果程序员准备显式指定其属性、方法和