为什么 PropertyDescriptor 的行为从 Java 1.6 更改为 1.7?

2022-09-03 15:58:33

更新:Oracle已确认这是一个错误。

简介: 在 JDK 1.6 中工作的某些自定义在 JDK 1.7 中失败,有些仅在垃圾回收运行并清除某些 SoftReference 后才会失败。BeanInfoPropertyDescriptor

编辑:这也将打破春季3.1中的帖子底部所述。ExtendedBeanInfo

编辑:如果您调用 JavaBeans 规范的第 7.1 或 8.3 节,请准确解释规范的这些部分在哪些方面需要任何内容。在这些章节中,该语言不是强制性的或规范性的。这些部分中的语言是示例的语言,作为规范,这些示例充其量是模棱两可的。此外,API专门允许更改默认行为,并且在下面的第二个示例中明显被破坏了。BeanInfo

Java Beans 规范查找具有 void 返回类型的默认 setter 方法,但它允许通过 .使用它的最简单方法是指定 getter 和 setter 的名称。java.beans.PropertyDescriptor

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;
    }
}

自定义 s 的示例允许在 Java Beans 规范中以编程方式控制 s,这些示例都使用 void 返回类型作为其 setter,但规范中没有任何内容表明这些示例是规范的,现在这个低级实用程序的行为在新的 Java 类中发生了变化,这恰好破坏了我正在处理的一些代码。BeanInfoPropertyDescriptor

在 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) {

现在,我们不再简单地接受具有正确名称和参数的方法,而是检查返回类型以查看它是否为 null,因此不再使用流利的 setter。在这种情况下,将抛出一个:“方法未找到:setI”。PropertyDescriptorPropertyDescriptorIntrospectionException

但是,这个问题比上面的简单测试要阴险得多。在 中指定 getter 和 setter 方法的另一种方法是使用实际的对象:PropertyDescriptorBeanInfoMethod

@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 时,出现任何问题的唯一迹象是。setter 为 null,大多数实用程序代码都认为该属性是只读的。PropertyDescriptor

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

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

在使用自定义时,随着时间的推移,行为会像这样变化,这可能会非常令人困惑。尝试复制导致垃圾回收器清除这些特定条件的条件也很乏味(尽管也许一些工具模拟可能会有所帮助)。BeanInfoSoftReferences

Spring课程有类似于上面的测试。这是一个实际的Spring 3.1.1单元测试,它将在单元测试模式下通过,但被测试的代码将在后GC阴险模式下失败:ExtendedBeanInfoExtendedBeanInfoTest

@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库中的一个错误。我错了吗,为什么?


答案 1

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

规范:

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

更具体地请参阅第 7.1 节(访问器方法)和 8.3 节(简单属性的设计模式)。

请参阅此堆栈溢出问题中的一些后续答案:

Java Bean 的 setter permit 是否返回此函数?


答案 2

第8.2节规定:

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

(着重号后加)

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


推荐