Почему поведение 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. Я не прав и почему?

 jbindel30 мая 2012 г., 02:22
Хип-дампы и VisualVM - мои друзья.
 Pointy30 мая 2012 г., 02:24
Можете ли вы уточнить или уточнить ситуацию, в которой выполняется размещенный код diff?
 biziclop29 мая 2012 г., 23:27
Я думаю, это заняло много времени.
 Prof. Falken29 мая 2012 г., 23:24
Хорошее описание.
 jbindel30 мая 2012 г., 02:42
Надеюсь, я все уточнил. Это вgetWriteMethod(), который вызывается конструктором первого примера. В этом случае, и после того, как GC очищает ссылку Soft, он видит установщик нуля и пытается разрешить это, но терпит неудачу в коде 1.7.

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

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

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

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

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

См. Некоторые из последующих ответов в этом вопрос

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

 Pointy30 мая 2012 г., 00:38
Возможность иметь API бинов, которые отличаются от настроек по умолчанию, выглядит как основа всего механизма BeanInfo, и так было с 90-х годов.
 jbindel30 мая 2012 г., 00:37
Разделах 7.1 и 8.3 приведены примеры «простых» и стандартных шаблонов для методов доступа, но они не Требуют все, что я могу определить.

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

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

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

 jbindel30 мая 2012 г., 02:39
Я могу посмотреть снова, но я верю, что конструктор, который принимаетString имена получателей и установщиков именgetWriteMethod() а это то, на что указывает diff.
 jbindel30 мая 2012 г., 02:10
Возвращаемый тип не является частью «сигнатуры метода», но, возможно, существует какое-то другое определение «сигнатуры типа». Docs.oracle.com / JavaSE / функции / JLS / se5.0 / html / classes.html # 8.4.2
 jbindel30 мая 2012 г., 04:57
Ничто в возвращаемом типе не является «существенным для [операции]» метода установки, поэтому я также не считаю это оправданием.
 jbindel30 мая 2012 г., 02:12
Re: 7.1 и 8.3, будучи нормативными, они написаны не так. В них нет «обязательных», «обязательных», «волевых» или «обязательных», поэтому мне сложно понять, как они требуются. И Oracle все еще нарушил поведение PropertyDescriptors в 1.7.
 Pointy30 мая 2012 г., 02:26
I считат что опубликованная разница между кодами от 1,6 до 1,7 выполняется только в тех случаях, когда PropertyDescriptor был утерян в GC. Таким образом, исполненnull возвращаемый тип проверяется толькопосл PropertyDescriptor, возможно, использовался в течение сколь угодно длительного периода времени!

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

Решение Вопроса

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 года ошибки видны во внешней базе данных по ссылкам выше.

 jbindel25 мар. 2014 г., 14:28
Я тоже это заметил, поэтому не знаю, в каком состоянии были ошибки. Можно проверить исходный код, чтобы увидеть, был ли он исправлен.
 Piotr Findeisen25 мар. 2014 г., 09:13
Вторая ссылка перенаправляет на Bugs.java.com / bugdatabase / view_bug.do? Bug_id = 7172865 и не доступен. Я вижу только заголовок второго, так как первые ссылки на него: «JDK-7172865 - PropertyDescriptor не может работать с именем метода установщика, если сеттер не является пустым». Печально, Оракул:
 Piotr Findeisen14 апр. 2014 г., 12:03
Кажется, достаточно нажать кнопку «Поиск снова» и ввести идентификатор ошибки в соответствующее поле, чтобы увидеть эти отчеты об ошибках. Во всяком случае, ничего оптимистичного. Тем более, что «getWriteMethod () не работает после того, как GC очищает SoftReferences», это верно для случаев использования с обобщениями, нет необходимости вызыватьsetWriteMethod чтобы иметь большие проблемы с этим.
 jbindel21 июн. 2012 г., 05:22
Ура, Oracle приняла сообщения об ошибках. : -)

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