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

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

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

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

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

Спецификация Java Beans ищет методы установки по умолчанию с возвращаемым типом void, но позволяет настраивать методы getter и setter через 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, которые позволяют программный контроль PropertyDescriptors в спецификации 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;
}

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

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

Во втором примере метод чтения и метод записи хранятся в SoftReference объекты в PropertyDescriptor конструктором, и сначала они будут содержать ссылки на readMethod а также writeMethod геттер и сеттер Methodдано конструктору. Если в какой-то момент эти программные ссылки будут очищены, поскольку сборщик мусора может это сделать (и это будет делать), то getWriteMethod() код увидит, что SoftReference возвращает null, и он попытается обнаружить сеттер. На этот раз, используя тот же путь кода внутри PropertyDescriptor это приводит к сбою первого примера в JDK 1.7, это установит запись Method в null потому что тип возвращаемого значения не void, (Тип возврата не является частью сигнатуры метода 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.

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

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

(Ошибки были представлены 30 мая 2012 года.)

По состоянию на 20 июня 2012 года ошибки видны во внешней базе данных по ссылкам выше.

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

Спецификация:

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

Более конкретно см. Раздел 7.1 (методы доступа) и 8.3 (шаблоны проектирования для простых свойств).

Посмотрите некоторые из последующих ответов в этом вопросе:

Разрешает ли установщик Java bean вернуть это?

Я также предпочел бы сказать, что запрещение не-пустых сеттеров - ошибка. Это просто делает свободное программирование невозможным. Вот почему это должно быть изменено.

Раздел 8.2 определяет:

Однако в Java Beans использование имен методов и типов, соответствующих шаблонам проектирования, совершенно необязательно. Если программист готов явно указать свои свойства, методы и события, используя интерфейс BeanInfo, он может вызывать свои методы и типы по своему усмотрению. Однако эти методы и типы все равно должны будут соответствовать требуемым сигнатурам типов, поскольку это важно для их работы.

(выделение добавлено)

Кроме того, я считаю, что сигнатуры методов, показанные в 7.1 и 8.3, фактически являются нормативными. Они являются примерами только в том смысле, что они используют "foo" в качестве имени свойства примера.

Другие вопросы по тегам