Обновление: 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. Я ошибаюсь и почему?