【问题标题】:Why did PropertyDescriptor behavior change from Java 1.6 to 1.7?为什么 PropertyDescriptor 行为从 Java 1.6 更改为 1.7?
【发布时间】:2012-05-29 21:21:41
【问题描述】:

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

总结:在 JDK 1.6 中工作的某些自定义 BeanInfos 和 PropertyDescriptors 在 JDK 1.7 中失败,并且一些仅在垃圾收集运行并清除某些 SoftReferences 后才会失败。

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

编辑:如果您调用 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 中可以指定设置器名称,即使它的返回类型不是 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;
    }
}

自定义BeanInfos 的示例,允许在Java Beans 规范中对PropertyDescriptors 进行编程控制,它们的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”。

然而,这个问题比上面的简单测试要隐蔽得多。在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 中都通过了单元测试,但是在 JVM 实例的生命周期中,由于相同的原因,代码将在某个时间点开始失败导致第一个示例立即失败的更改。在第二个示例中,当尝试使用自定义 PropertyDescriptor 时,唯一表明出现任何问题的迹象。 setter 为 null,大多数实用程序代码都认为该属性是只读的。

差异中的代码在PropertyDescriptor.getWriteMethod() 内。它在持有实际设置器MethodSoftReference 为空时执行。此代码由第一个示例中的PropertyDescriptor 构造函数调用,该构造函数采用上面的访问器方法names,因为最初没有Method 保存在保存实际getter 和setter 的SoftReferences 中。

在第二个示例中,构造函数将读取方法和写入方法存储在PropertyDescriptor 中的SoftReference 对象中,首先这些将包含对readMethodwriteMethod getter 和setter @987654351 的引用@s 给构造函数。如果在某些时候这些软引用被清除,因为垃圾收集器被允许这样做(它会这样做),那么getWriteMethod() 代码将看到SoftReference 返回 null,它会尝试发现设置器。 这一次,在 JDK 1.7 中使用导致第一个示例失败的 PropertyDescriptor 内部相同的代码路径,它将写入 Method 设置为 null,因为返回类型不是 @987654357 @。 (返回类型不是 Java method signature 的一部分。)

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

Spring ExtendedBeanInfo 类具有与上述类似的测试。这是来自ExtendedBeanInfoTest 的实际 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 一起工作。这似乎可行,但这是对 JDK 1.7 中更改的行为的一种破解。

问:有没有明确的规范说明非空二传手应该被诅咒?我什么也没找到,我目前认为这是 JDK 1.7 库中的一个错误。 我错了吗,为什么?

【问题讨论】:

  • 我想这需要很长时间才能弄清楚。
  • 堆转储和 VisualVM 是我的朋友。
  • 您能否澄清或详细说明发布的代码差异执行的情况?
  • 我希望我澄清了。它在getWriteMethod() 中,由第一个示例的构造函数调用。在这种情况下,在 GC 清除 Soft 引用后,它会看到一个 null setter 并尝试解决它,但在 1.7 代码中失败了。

标签: java spring javabeans java-7


【解决方案1】:

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

规格:

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

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

在这个 stackoverflow 问题中查看后面的一些答案:

Does Java bean's setter permit return this?

【讨论】:

  • 第 7.1 节和第 8.3 节给出了访问器的“简单”模式和默认模式的示例,但据我所知,它们并不需要任何东西。
  • 能够拥有偏离默认值的 bean API 似乎是整个 BeanInfo 机制的基本点,自 90 年代以来一直如此。
【解决方案2】:

第 8.2 节规定:

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

(强调)

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

【讨论】:

  • 返回类型不是“方法签名”的一部分,但可能对“类型签名”有一些不同的定义。 docs.oracle.com/javase/specs/jls/se5.0/html/classes.html#8.4.2
  • Re: 7.1 和 8.3 是规范性的,它们不是这样写的。其中没有“必需”、“必须”、“将”或“应”,所以我很难看出它们是如何被要求的。而且 Oracle 在 1.7 中仍然破坏了 PropertyDescriptors 的行为。
  • 认为只有在 PropertyDescriptor 丢失给 GC 的情况下才会执行 1.6 和 1.7 代码之间的发布差异。因此,null 返回类型的强制执行仅在 PropertyDescriptor 可能已使用任意长的时间后检查!
  • 我可以再看一遍,但我相信采用 String getter 和 setter 名称的构造函数也会调用 getWriteMethod(),这就是 diff 指向的内容。
  • 返回类型对于 setter 方法的“操作必不可少”,所以我也不认为这是一个理由。
【解决方案3】:

我也会选择说禁止非空设置器是一个错误。它只会让流畅的编程变得不可能。这就是为什么它需要改变。

【讨论】:

    【解决方案4】:

    由于我发现 Spring 3.1.1 ExtendedBeanInfo 单元测试期望代码不会被破坏,并且因为在垃圾收集后改变行为显然是一个错误,所以我将回答这个问题并记下 Java 错误编号。这些错误在 Java 错误的外部数据库中仍然不可见,但我希望它们会在某个时候变得可见:

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

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

    (错误于 2012 年 5 月 30 日提交。)

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

    【讨论】:

    • 第二个链接重定向到bugs.java.com/bugdatabase/view_bug.do?bug_id=7172865 并且无法访问。我只能看到第二个的标题,因为第一个链接到它:“JDK-7172865 - 如果 setter 是非无效的,PropertyDescriptor 无法使用 setter 方法名称”。悲伤,甲骨文 :(
    • 我也注意到了,所以我不知道错误的状态。可以查看源代码,看看是否已修复。
    • 按下“再次搜索”按钮并在适当的字段中输入错误 ID 以查看这些错误报告似乎就足够了。无论如何,没有什么可乐观的。特别是因为“在 GC 清除 SoftReferences 后 getWriteMethod() 被破坏”对于泛型的用例是正确的,因此无需调用 setWriteMethod 来解决这个问题。
    猜你喜欢
    • 1970-01-01
    • 2023-04-03
    • 2014-08-02
    • 2013-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-03
    • 2020-04-28
    相关资源
    最近更新 更多