Почему поведение PropertyDescriptor изменилось с Java 1.6 до 1.7?
Обновление: Oracle подтвердила это как ошибку.
Сводка: Определенные пользовательские BeanInfo
и PropertyDescriptor
, которые работают в JDK 1.6, не работают в JDK 1.7, а некоторые только после сбоя Garbage Collection запускают и очищают определенные SoftReferences.
Изменить: это также сломает ExtendedBeanInfo
в Spring 3.1, как указано в нижней части сообщения.
Изменить: если вы вызываете разделы 7.1 или 8.3 спецификации JavaBeans, объясните точно, где те части спецификации требуют что-либо. язык не является обязательным или нормативным в этих разделах. язык в этих разделах - это примеры, которые в лучшем случае двусмысленный как спецификация. Кроме того, API BeanInfo
в частности, позволяет изменить поведение по умолчанию, и это явно нарушена во втором примере ниже.
Спецификация 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;
}
}
Пример пользовательского BeanInfo
s, который позволяет программному управлению PropertyDescriptor
в спецификации Java Beans использовать все типы void return для своих сеттеров, но ничто в спецификации не указывает, что эти примеры являются нормативными, и теперь поведение этой низкоуровневой утилиты изменилось в новых классах 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".
Однако проблема намного более коварна, чем простой тест выше. Другой способ указать методы getter и setter в 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;
}
Теперь приведенный выше код передаст unit test как в 1.6, так и в 1.7, но код начнет сбой в какой-то момент времени в течение срока действия экземпляра JVM из-за того же изменения, которое вызывает первый пример сбой немедленно. Во втором примере единственное указание на то, что что-то пошло не так, возникает при попытке использовать пользовательский PropertyDescriptor
. Setter имеет значение null, и в большинстве случаев код утилиты принимает значение, означающее, что свойство доступно только для чтения.
Код в diff находится внутри PropertyDescriptor.getWriteMethod()
. Он выполняется, когда SoftReference
, содержащий фактический установщик Method
, пуст. Этот код вызывается конструктором PropertyDescriptor
в первом примере, который принимает имена метода доступа выше, потому что изначально нет Method
, сохраненных в SoftReference
, содержащих фактический геттер и сеттер.
Во втором примере метод чтения и метод записи хранятся в SoftReference
объектах в PropertyDescriptor
конструктором, и сначала они будут содержать ссылки на readMethod
и writeMethod
getter и setter Method
, заданный конструктору. Если в какой-то момент эти Soft-ссылки очищаются, так как сборщик мусора разрешен (и он будет делать), тогда код getWriteMethod()
увидит, что SoftReference
возвращает значение null, и он попытается обнаружить установщика. На этот раз, используя тот же путь кода внутри PropertyDescriptor
, который приведет к сбою первого примера в JDK 1.7, он установит запись Method
в null
, потому что тип возврата не является void
. (Тип возврата не является частью сигнатуры метода .
Наличие изменения поведения, подобного этому со временем при использовании пользовательского BeanInfo
, может быть чрезвычайно запутанным. Пытаться дублировать условия, которые заставляют сборщик мусора очищать те, что относятся к SoftReferences
, также утомительно (хотя, возможно, может быть полезно какое-то инструментальное издевательство).
Класс Spring ExtendedBeanInfo
имеет тесты, аналогичные приведенным выше. Вот фактический Spring 3.1.1 unit test из ExtendedBeanInfoTest
, который пройдет в режиме unit test, но проверенный код завершится неудачно в пост-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));
}
Одно из предложений заключается в том, что мы можем поддерживать текущий код с не-void сеттерами, не позволяя методам setter быть только доступным для достижения цели. Кажется, что это сработает, но это скорее взломать измененное поведение в JDK 1.7.
Q: Есть ли какая-то определенная спецификация, указывающая, что не-void сеттеры должны быть анафемой? Я ничего не нашел, и в настоящее время считаю это ошибкой в библиотеках JDK 1.7.
Я ошибаюсь и почему?
Ответы
Ответ 1
Так как я нашел 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 года ошибки отображаются во внешней базе данных по ссылкам выше.
Ответ 2
Похоже, что спецификация не изменилась (для нее требуется недействительный сеттер), но реализация была обновлена, чтобы разрешить создание сетки void.
Спецификация:
http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html
Более подробно см. раздел 7.1 (методы доступа) и 8.3 (шаблоны проектирования для простых свойств)
См. некоторые более поздние ответы в этом вопросе stackoverflow:
Разрешает ли сеттер Java bean это?
Ответ 3
В разделе 8.2 указаны:
Однако в Java Beans использование имен методов и типов, соответствующих шаблонам проектирования, является необязательным. Если программист готов явно указать свои свойства, методы и события, используя интерфейс BeanInfo, тогда они могут вызывать свои методы и типы, которые им нравятся. Однако эти методы и типы все равно должны будут соответствовать требуемым типам подписей, так как это важно для их работы.
(выделено курсивом)
Кроме того, я верю, что сигнатуры методов, показанные в 7.1 и 8.3, фактически являются нормативными. Они являются примерами только в том смысле, что они используют "foo" в качестве имени свойства примера.
Ответ 4
Я также хотел бы сказать, что отказ от не-void seters - ошибка. Это просто делает свободное программирование невозможным. Вот почему это нужно изменить.