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

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

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

Edit: This will also break the ExtendedBeanInfo in Spring 3.1 as noted at the bottom of the post.

Edit: 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, но позволяет настраивать методы получения и установки через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;
    }
}

Пример кастомаBeanInfos, которые позволяют программный контрольPropertyDescriptorВ спецификации Java Beans все используют типы возвращаемых значений void для своих сеттеров, но ничто в спецификации не указывает на то, что эти примеры являются нормативными, и теперь поведение этой низкоуровневой утилиты изменилось в новых классах Java, которые, как оказалось, сломались какой-то код, над которым я работаю.

Есть многочисленные изменения вjava.beans пакет между JDK 1.6 и 1.7, но тот, который вызывает этот тест, кажется, находится в этом 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 теперь также проверяет тип возвращаемого значения, чтобы определить, является ли он пустым, поэтому установщик беглого языка больше не используется.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;
}

Теперь приведенный выше кодwill пройти модульное тестирование как в 1.6, так и в 1.7, но код начнет давать сбой в какой-то момент времени в течение срока службы экземпляра JVM из-за того же самого изменения, которое приводит к немедленному отказу первого примера. Во втором примере единственным признаком того, что что-то пошло не так, является попытка использоватьPropertyDescriptor, Сеттер имеет значение null, и в большинстве служебных программ это значение означает, что свойство доступно только для чтения.

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

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

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

Весна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.

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

Ответы на вопрос(4)

Ваш ответ на вопрос