为什么PropertyDescriptor行为从Java 1.6变为1.7?
更新:Oracle已将此确认为错误。
Update: Oracle has confirmed this as a bug.
摘要:某些自定义 BeanInfo
s和 c> PropertyDescriptor 在JDK 1.7中失败,有些只在垃圾收集运行并且清除了某些SoftReferences后失败。
Summary: Certain custom BeanInfo
s and PropertyDescriptor
s that work in JDK 1.6 fail in JDK 1.7, and some only fail after Garbage Collection has run and cleared certain SoftReferences.
编辑:这也会破坏Spring 3.1中的 ExtendedBeanInfo
,如帖子底部所示。
This will also break the
ExtendedBeanInfo
in Spring 3.1 as noted at the bottom of the post.
编辑:如果您调用JavaBeans规范的第7.1或8.3节,请解释
与规范的那些部分的确切位置。
语言在这些部分中不是强制性的或规范性的。这些部分中的
语言就是一些例子,最多只有
作为规范。此外, BeanInfo
API
特别允许一个人更改默认行为,并且在下面的第二个示例中明确区分为
。
If you invoke sections 7.1 or 8.3 of the JavaBeans spec, explain
exactly where those parts of the spec require anything. The
language is not imperative or normative in those sections. The
language in those sections is that of examples, which are at best
ambiguous as a specification. Furthermore, the BeanInfo
API
specifically allows one to change the default behavior, and it is
clearly broken in the second example below.
Java Beans规范查找具有void返回类型的默认setter方法,但它允许通过 java.beans.PropertyDescriptor中。最简单的方法是指定getter和setter的名称。
The Java Beans specification looks for default setter methods with a void return type, but it allows customization of the getter and setter methods through a java.beans.PropertyDescriptor
. The simplest way to use it has been to specify the names of the getter and setter.
new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");
这在JDK 1.5和JDK 1.6中有用,指定了setter名称,即使它的返回类型不是如以下测试用例中的void:
This has worked in JDK 1.5 and JDK 1.6 to specify the setter name even when its return type is not void as in the test case below:
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
进行编程控制,它们都为其setter使用void返回类型,但规范中没有任何内容表明这些示例是规范性的,现在这个低级实用程序的行为在新的Java类中发生了变化,这恰好打破了我正在工作的一些代码。
The example of custom BeanInfo
s, which allow the programmatic control of PropertyDescriptor
s in the Java Beans specification all use void return types for their setters, but nothing in the specification indicates that those examples are normative, and now the behavior of this low-level utility has changed in the new Java classes, which happens to have broken some code on which I am working.
JDK 1.6和1.7之间的 java.beans
包中有很多变化,但导致此测试失败的变化似乎在这个差异中:
There are numerous changes in in the java.beans
package between JDK 1.6 and 1.7, but the one that causes this test to fail appears to be in this diff:
@@ -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。
Instead of simply accepting the method with the correct name and parameters, the PropertyDescriptor
is now also checking the return type to see whether it is null, so the fluent setter no longer gets used. The PropertyDescriptor
throws an IntrospectionException
in this case: "Method not found: setI".
然而,问题比上面的简单测试更加隐蔽。在 PropertyDescriptor
中为自定义 BeanInfo
指定getter和setter方法的另一种方法是使用实际的方法
对象:
However, the problem is much more insidious than the simple test above. Another way to specify the getter and setter methods in the PropertyDescriptor
for a custom BeanInfo
is to use the actual Method
objects:
@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,大多数实用程序代码都认为该属性是只读的。
Now the above code will pass a unit test in both 1.6 and in 1.7, but the code will begin to fail at some point in time during the life of the JVM instance owing to the very same change that causes the first example to fail immediately. In the second example the only indication that anything has gone wrong comes when trying to use the custom PropertyDescriptor
. The setter is null, and most utility code takes that to mean that the property is read-only.
diff中的代码位于 PropertyDescriptor中.getWriteMethod()
。当 SoftReference
持有实际的setter Method
为空时执行。此代码由第一个示例中的 PropertyDescriptor
构造函数调用,该构造函数采用上面的访问器方法 names ,因为最初没有方法
保存在 SoftReference
中,保存实际的getter和setter。
The code in the diff is inside PropertyDescriptor.getWriteMethod()
. It executes when the SoftReference
holding the actual setter Method
is empty. This code is invoked by the PropertyDescriptor
constructor in the first example that takes the accessor method names above because initially there is no Method
saved in the SoftReference
s holding the actual getter and setter.
在第二个例子,构造函数将read方法和write方法存储在 PropertyDescriptor
中的 SoftReference
对象中,首先这些将包含对 readMethod
和 writeMethod
getter和setter 方法
的引用>给予构造函数。如果在某些时候这些Soft引用被清除,因为垃圾收集器被允许(并且它会这样做),那么 getWriteMethod()
代码将看到 SoftReference
返回null,它将尝试发现setter。 这次,在 PropertyDescriptor
中使用相同的代码路径导致第一个示例在JDK 1.7中失败,它将设置写入方法
到 null
因为返回类型不是 void
。 (返回类型是不 Java 方法签名。)
In the second example the read method and write method are stored in SoftReference
objects in the PropertyDescriptor
by the constructor, and at first these will contain references to the readMethod
and writeMethod
getter and setter Method
s given to the constructor. If at some point those Soft references are cleared as the Garbage Collector is allowed to do (and it will do), then the getWriteMethod()
code will see that the SoftReference
gives back null, and it will try to discover the setter. This time, using the same code path inside PropertyDescriptor
that causes the first example to fail in JDK 1.7, it will set the write Method
to null
because the return type is not void
. (The return type is not part of a Java method signature.)
使用自定义 BeanInfo时,行为会随着时间的推移而改变
可能非常令人困惑。试图复制导致垃圾收集器清除那些特定的 SoftReferences
的条件也很繁琐(尽管可能有些工具嘲弄可能会有所帮助。)
Having the behavior change like this over time when using a custom BeanInfo
can be extremely confusing. Trying to duplicate the conditions that cause the Garbage Collector to clear those particular SoftReferences
is also tedious (though maybe some instrumenting mocking may help.)
Spring ExtendedBeanInfo
类的测试类似于上面的测试。以下是来自 ExtendedBeanInfoTest
的实际Spring 3.1.1单元测试,它将在单元测试模式下传递,但正在测试的代码将在后GC阴险模式下失败::
The Spring ExtendedBeanInfo
class has tests similar to those above. Here is an actual Spring 3.1.1 unit test from ExtendedBeanInfoTest
that will pass in unit test mode, but the code being tested will fail in the post-GC insidious mode::
@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方法只能轻柔地访问来设置setter。这似乎可行,但这是对JDK 1.7中改变行为的破解。
One suggestion is that we can keep the current code working with the non-void setters by preventing the setter methods from being only softly reachable. That seems like it would work, but that is rather a hack around the changed behavior in JDK 1.7.
问:是否有一些明确的规范说明非空的设置者应该是诅咒吗?我什么都没找到,我现在认为这是JDK 1.7库中的一个错误。
我错了,为什么?
Q: Is there some definitive specification stating that non-void setters should be anathema? I've found nothing, and I currently consider this a bug in the JDK 1.7 libraries. Am I wrong, and why?
因为我找到了Spring 3.1.1 ExtendedBeanInfo
期望代码不被破坏的单元测试,并且因为垃圾收集后行为发生变化显然是一个错误,我将回答这个并记下Java错误号。这些错误在Java错误的外部数据库中仍然不可见,但我希望它们在某些时候可见:
Since I found Spring 3.1.1 ExtendedBeanInfo
unit tests that expect the code not to be broken, and because having the behavior change after garbage collection is obviously a bug, I shall answer this and note the Java bug numbers. The bugs are still not visible on the external database of Java bugs, but I hope that they will become visible at some point:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7172854 (甲骨文关闭了这个尽管存在不同的表现形式,但由于它们具有相同的原因,因此下面的错误是重复的。)
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7172854 (Oracle closed this as a duplicate of the bug below since they have the same cause despite different manifestations.)
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7172865
(这些错误是在2012年5月30日提交的。)
(The bugs were submitted on May 30, 2012.)
截至2012年6月20日,通过上面的链接可以在外部数据库中看到错误。
As of June 20, 2012, the bugs are visible in the external database via the links above.