为什么PropertyDescriptor行为从Java 1.6变为1.7?
更新:Oracle已将此确认为错误。
简介:在JDK 1.7中工作的某些自定义BeanInfo
和PropertyDescriptor
在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 Method
的SoftReference
为空时执行。 此代码由第一个示例中的PropertyDescriptor
构造函数调用,该构造函数采用上面的访问器方法名称 ,因为最初没有在SoftReference
保存的Method
保存实际的getter和setter。
在第二个示例中,read方法和write方法由构造函数存储在PropertyDescriptor
中的SoftReference
对象中,并且首先它们将包含对构造函数的readMethod
和writeMethod
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 ExtendedBeanInfo
unit testing期望代码不被破坏,并且因为在垃圾收集之后行为改变显然是一个错误,我将回答这个并记下Java错误号。 这些bug在Java漏洞的外部数据库中仍然不可见,但我希望它们在某些时候可见:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7172865
(这些错误是在2012年5月30日提交的。)
截至2012年6月20日,通过上面的链接可以在外部数据库中看到错误。
- javax.net.ssl.SSLHandshakeException:收到致命警报:unknown_ca
- 当没有读取带有URL的QR码时,zxing QRCodeReader中的ChecksumException
- 为什么DecimalFormat允许字符作为后缀?
- Java RegEx Matcher.groupCount返回0
- ClassNotFoundException:org.hibernate.hql.internal.ast.HqlToken甚至在添加了classic.ClassicQueryTranslatorFactory 之后
- 如何使用弹簧的分层架构仍然遵循面向对象的结构?
- Java – 通过对象数组在扩展类中调用函数
- 选择具有多个选项的覆盖方法java
- 有没有办法从Java中的响应对象中读取cookie?