当前位置: 首页 > 知识库问答 >
问题:

为什么PropertyDescriptor行为从Java1.6改为1.7?

孔斌
2023-03-14

更新:Oracle已经确认这是一个bug。

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

new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");

这在JDK1.5和JDK1.6中起到了作用,即使它的返回类型不是void,也可以指定setter名称,如下面的测试用例所示:

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的示例允许对propertydescriptor编程控制,在Java Beans规范中,这些示例都使用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抛出InspectionException在本例中:“method not found:seti”。

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

@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,大多数实用程序代码认为这意味着属性是只读的。

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

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

在使用自定义beaninfo时,随着时间的推移使行为发生这样的变化可能会非常混乱。试图复制导致垃圾回收器清除那些特定softreferences的条件也是很繁琐的(尽管一些工具化的嘲弄可能会有帮助)

SpringExtendedBeanInfo类具有与上述类似的测试。下面是来自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一起工作。这看起来是可行的,但这是对JDK1.7中更改的行为的一次攻击。

问:是否有一些明确的规范说明非空设置器应该是可憎的?我没有发现任何东西,目前我认为这是JDK1.7库中的一个bug。我错了吗?为什么?

共有1个答案

沙宣
2023-03-14

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

规格:

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

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

Java bean的setter允许返回这个吗?

 类似资料:
  • 问题内容: 在我的Web应用程序中,我在Apache Tomcat(TomEE)/7.0.37服务器上使用OpenJPA。我使用Netbeans自动生成类(“来自数据库的实体类…”和“来自实体类的会话Bean …”)。在SessionBean(例如UserFacade)上,我想获取EntityManager: 但是当我通过上述方式得到它时,我得到的是空值。当我通过: ecm不为空,还可以 我的pe

  • 问题内容: 我有Ubuntu 10.10和apache2,php 5.3.3-1和mysql 5.1。 我正在通过URL向页面传递一些值。在该页面上,如果我这样做了,那么我会看到数组的内容。但是,如果我这样做数组是空的。任何想法为什么会这样? 问题答案: 也可以尝试检查php.ini中的“ request_order” 选项:

  • 我的问题是无法使用和获取文本字段。我尝试使用XPath来选择对象,但没有成功。 这是我的代码: 我重新提出这个问题是因为旧的问题有点让人困惑,我想。这是旧的 XML形式的我的文档(Document.getXML()) 我需要选择文本字段,做一个邮件合并,我的计划将是复制和移动字段。如果有更好的方法,我愿意尝试一下:)

  • 问题内容: 我知道在某些版本中,Hibernate异常已更改为未经检查。是什么原因?这是哲学问题还是实际问题? 问题答案: 实际的。因此,您不必将有关Hibernate的每一项操作都包装在try catch块中。 摘自Hibernate的Java Persistence: 异常的历史-异常及其应如何处理始终以Java开发人员之间的激烈辩论而告终。hibernate也具有一些值得注意的历史也就不足为

  • 问题内容: var x int done := false go func() { x = f(…); done = true } while done == false { } 这是Go代码。我的恶魔告诉我,这是UB代码。为什么? 问题答案: Go Memory Model不保证该程序将始终遵守在goroutine中写入x的值。go常规销毁 部分中提供了一个类似的错误程序作为示例。 在本节中,G

  • 问题内容: 我有一些调用的代码。 不过,这将返回null。 当我从命令行而不是从Eclipse启动相同的代码时,它将返回一个类加载器。 我可以破解代码来做到这一点… 两者都被编译并使用相同的JVM运行。(我确定99.99%)。 任何人都知道为什么第一个会为类加载器返回null? 编辑: 我的问题是:“没有人知道为什么同一类通过Eclipse启动时会返回null,而从命令行加载时会返回类加载器。”