Почему поведение PropertyDescriptor изменилось с Java 1.6 на 1.7?

Обновление: Oracle подтвердила, что это ошибка.

Резюме: некоторые пользовательские BeanInfoи PropertyDescriptor, которые работают в JDK 1.6, не работают в JDK 1.7, а некоторые терпят неудачу только после того, как сборщик мусора запустился и очистил определенные SoftReferences.

Изменить: это также сломает ExtendedBeanInfoв Spring 3.1, как указано в нижней части поста.

Изменить: Если вы вызываете разделы 7.1 или 8.3 спецификации JavaBeans, объясните именно там, где эти части спецификации требуютчего-либо. То язык не является императивным или нормативным в этих разделах. То язык в этих разделах - это язык примеров, которые в лучшем случае неоднозначно как спецификация. Кроме того, API BeanInfo специально позволяет изменить поведение по умолчанию, и это явно сломан во втором примере ниже.

Спецификация Java Beans ищет методы установки по умолчанию с возвращаемым типом void, но позволяет настраивать методы получения и установки с помощью java.beans.PropertyDescriptor. Самый простой способ его использования — указать имена геттера и сеттера.

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

Это работало в JDK 1.5 и JDK 1.6, чтобы указать имя установщика, даже если его тип возвращаемого значения не пуст, как в приведенном ниже тестовом примере:

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, которые позволяют программно управлять PropertyDescriptors в спецификации Java Beans все используют возвращаемые типы void для своих сеттеров, но ничто в спецификации не указывает на то, что эти примеры являются нормативными, и теперь поведение этой низкоуровневой утилиты изменилось в новых классах Java, которые, как оказалось, нарушили некоторый код, над которым я работаю.

В пакет java.beansмежду JDK 1.6 и 1.7 внесены многочисленные изменения, но то, что приводит к сбою этого теста, похоже, находится в этом различии:

@@ -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теперь также проверяет тип возвращаемого значения, чтобы определить, является ли он нулевым, поэтому плавный сеттер больше не используется. PropertyDescriptorвыдает IntrospectionExceptionв этом случае: «Метод не найден: setI».

Однако проблема гораздо более коварна, чем простой тест выше. Другой способ указать методы получения и установки в PropertyDescriptorдля пользовательского BeanInfo— использовать фактические объекты 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. Сеттер имеет значение null, и в большинстве служебных программ это означает, что свойство доступно только для чтения.

Код в разнице находится внутри PropertyDescriptor.getWriteMethod(). Он выполняется, когда SoftReference, содержащий фактический установщик Method, пуст. Этот код вызывается конструктором PropertyDescriptorв первом примере, который использует метод доступа nameвыше, потому что изначально в SoftReference не сохранен метод Method. содержит геттер и сеттер.

Во втором примере метод чтения и метод записи сохраняются в объектах SoftReferenceв PropertyDescriptorконструктором, и сначала они будут содержать ссылки на readMethod и writeMethodgetter и setter Methodпередаются конструктору. Если в какой-то момент эти Soft-ссылки будут очищены, как разрешено делать сборщику мусора (и это будет сделано), то код getWriteMethod()увидит, что SoftReferenceвозвращает null , и он попытается обнаружить сеттер. На этот раз, используя тот же путь кода внутри PropertyDescriptor, который приводит к сбою первого примера в JDK 1.7, он установит для записи Methodзначение null , так как возвращаемый тип не void.(Тип возвращаемого значения нечасть сигнатуры метода Java .)

Такое изменение поведения с течением времени при использовании пользовательского BeanInfoможет быть крайне запутанно. Попытка продублировать условия, которые заставляют сборщик мусора очищать эти конкретные SoftReferences, также утомительна (хотя, возможно, некоторые инструментальные насмешки могут помочь).

Класс Spring ExtendedBeanInfoимеет тесты, аналогичные приведенным выше. Вот фактический модульный тест Spring 3.1.1 из ExtendedBeanInfoTest, который пройдет в режиме модульного тестирования, но тестируемый код не будет работать в коварном режиме после 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));
}

Одно из предположений состоит в том, что мы может сохранить текущий код, работающий с непустыми сеттерами, предотвращая только программную доступность методов сеттера. Похоже, это сработает, но это скорее хак вокруг измененного поведения в JDK 1.7.

В: Есть ли определенная спецификация, утверждающая, что непустые сеттеры должны быть преданы анафеме? Я ничего не нашел и сейчас считаю это ошибкой в ​​библиотеках JDK 1.7. Я ошибаюсь и почему?

15
задан jbindel 21 June 2012 в 20:41
поделиться