为什么PropertyDescriptor行为从Java 1.6变为1.7?

更新:Oracle已将此确认为错误。

简介:在JDK 1.7中工作的某些自定义BeanInfoPropertyDescriptor在JDK 1.7中失败,有些只在垃圾收集运行并且清除了某些SoftReferences后才会失败。

编辑:这也将破坏Spring 3.1中的ExtendedBeanInfo ,如post底部所述。

编辑:如果您调用JavaBeans规范的第7.1或8.3节,请准确说明规范的这些部分需要什么。 这些部分中的语言不是必要的或规范性的。 这些部分中的语言是示例的语言,这些语言最多不像规范那样含糊不清。 此外, BeanInfo API特别允许人们更改默认行为,并且在下面的第二个示例中明确区分了它。

Java Beans规范查找具有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的示例允许对Java Beans规范中的PropertyDescriptor进行编程控制,它们都为其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,而不是简单地接受具有正确名称和参数的方法,因此不再使用流畅的setter。 在这种情况下, PropertyDescriptor会抛出一个IntrospectionException :“找不到方法:setI”。

然而,问题比上面的简单测试更加隐蔽。 在PropertyDescriptor为自定义BeanInfo指定getter和setter方法的另一种方法是使用实​​际的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中通过unit testing,但代码将在JVM实例的生命周期中的某个时间点开始失败,原因是导致第一个示例立即失败的相同更改。 在第二个示例中,当尝试使用自定义PropertyDescriptor时,出现任何问题的唯一指示。 setter为null,大多数实用程序代码都认为该属性是只读的。

diff中的代码位于PropertyDescriptor.getWriteMethod() 。 当持有实际setter MethodSoftReference为空时执行。 此代码由第一个示例中的PropertyDescriptor构造函数调用,该构造函数采用上面的访问器方法名称 ,因为最初没有在SoftReference保存的Method保存实际的getter和setter。

在第二个示例中,read方法和write方法由构造函数存储在PropertyDescriptor中的SoftReference对象中,并且首先它们将包含对构造函数的readMethodwriteMethod getter以及setter Method的引用。 如果在某些时候这些Soft引用被清除,因为垃圾收集器被允许(并且它会这样做),那么getWriteMethod()代码将看到SoftReference返回null,它将尝试发现setter。 这次 ,在PropertyDescriptor中使用相同的代码路径导致第一个示例在JDK 1.7中失败,它将write Method设置为null因为返回类型不是void 。 (返回类型不是 Java 方法签名的一部分 。)

使用自定义BeanInfo时,随着时间的推移,这种行为会发生变化,这可能会BeanInfo非常困惑。 尝试复制导致垃圾收集器清除这些特定SoftReferences条件也很繁琐(尽管某些工具模拟可能会有所帮助。)

Spring ExtendedBeanInfo类具有与上述类似的测试。 这是一个来自ExtendedBeanInfoTest的实际Spring 3.1.1unit testing,它将在unit testing模式下传递,但正在测试的代码将在后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库中的一个错误。 我错了,为什么?

看起来规范没有改变(它需要void setter)但实现已更新为仅允许void setter。

规范:

http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html

更具体地说,参见7.1节(访问器方法)和8.3(简单属性的设计模式)

请参阅此stackoverflow问题中的一些后续答案:

Java bean的setter许可证是否会返回?

第8.2节规定:

但是,在Java Bean中,使用与设计模式匹配的方法和类型名称是完全可选的。 如果程序员准备使用BeanInfo接口显式指定其属性,方法和事件,那么他们可以调用他们喜欢的方法和类型。 但是, 这些方法和类型仍然必须匹配所需的类型签名,因为这对其操作至关重要

(重点补充)

另外,我相信7.1和8.3中显示的方法签名实际上是规范性的。 它们仅是示例,它们使用“foo”作为示例属性名称。

我也会选择拒绝禁止无效的setter是一个错误。 它只是简化了流畅的编程 。 这就是为什么需要改变它。

由于我发现Spring 3.1.1 ExtendedBeanInfounit testing期望代码不被破坏,并且因为在垃圾收集之后行为改变显然是一个错误,我将回答这个并记下Java错误号。 这些bug在Java漏洞的外部数据库中仍然不可见,但我希望它们在某些时候可见:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7172854(Oracle将此作为下面的错误的副本关闭,因为它们具有相同的原因,尽管有不同的表现forms。)

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7172865

(这些错误是在2012年5月30日提交的。)

截至2012年6月20日,通过上面的链接可以在外部数据库中看到错误。