Dlaczego zachowanie PropertyDescriptor zmieniło się z Java 1.6 na 1.7?
Aktualizacja: Oracle potwierdziło to jako błąd.
Podsumowanie: Pewny zwyczajBeanInfo
s iPropertyDescriptor
s działające w JDK 1.6 nie działają w JDK 1.7, a niektóre zawodzą tylko po uruchomieniu Garbage Collection i usunięciu pewnych SoftReferences.
Edytuj: spowoduje to również przerwanieExtendedBeanInfo
wiosną 3.1, jak zaznaczono na dole postu.
Edytuj: Jeśli wywołujesz sekcje 7.1 lub 8.3 specyfikacji JavaBeans, wyjaśnij dokładnie, gdzie te części specyfikacjiwymagać byle co. Język nie jest konieczny ani normatywny w tych sekcjach. Język w tych sekcjach jest językiem przykładów, które w najlepszym razie są niejednoznaczne jako specyfikacja. PonadtoBeanInfo
Interfejs API pozwala konkretnie zmienić domyślne zachowanie i jest wyraźnie zepsuty w drugim przykładzie poniżej.
Specyfikacja Java Beans szuka domyślnych metod ustawiających z typem powrotu pustego, ale umożliwia dostosowanie metod pobierających i ustawiających za pomocąjava.beans.PropertyDescriptor
. Najprostszym sposobem użycia jest określenie nazw gettera i settera.
new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");
W JDK 1.5 i JDK 1.6 zadziałało to, aby określić nazwę selektora, nawet jeśli jego typ powrotu nie jest nieważny, jak w poniższym przypadku testowym:
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;
}
}
Przykład zwyczajuBeanInfo
s, które umożliwiają programową kontrolęPropertyDescriptor
s w specyfikacji Java Beans używają typów zwrotów void dla swoich ustawników, ale nic w specyfikacji nie wskazuje, że te przykłady są normatywne, a teraz zachowanie tego narzędzia niskiego poziomu zmieniło się w nowych klasach Java, co zdarza się, że uległo uszkodzeniu jakiś kod, na którym pracuję.
Istnieje wiele zmian wjava.beans
pakiet między JDK 1.6 a 1.7, ale ten, który powoduje, że test się nie powiedzie, wydaje się być w tej różnicy:
@@ -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) {
Zamiast po prostu zaakceptować metodę z poprawną nazwą i parametrami,PropertyDescriptor
sprawdza teraz także typ powrotu, aby sprawdzić, czy jest on pusty, więc płynny setter nie jest już używany. ThePropertyDescriptor
rzucaIntrospectionException
w tym przypadku: „Nie znaleziono metody: setI”.
Jednak problem jest znacznie bardziej podstępny niż prosty test powyżej. Inny sposób określenia metod pobierających i ustawiających wPropertyDescriptor
dla zwyczajuBeanInfo
jest użycie rzeczywistegoMethod
obiekty:
@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;
}
Teraz powyższy kodbędzie przejdzie test jednostkowy zarówno w wersji 1.6, jak i 1.7, ale kod zacznie zawodzić w pewnym momencie w czasie trwania instancji JVM z powodu tej samej zmiany, która powoduje natychmiastowy błąd pierwszego przykładu. W drugim przykładzie jedynym wskazaniem, że coś poszło nie tak, jest próba użycia tego zwyczajuPropertyDescriptor
. Setter ma wartość null, a większość kodu narzędzia oznacza, że właściwość jest tylko do odczytu.
Kod w diff jest wewnątrzPropertyDescriptor.getWriteMethod()
. Wykonuje się, gdySoftReference
trzymając rzeczywisty seterMethod
jest pusty. Ten kod jest wywoływany przezPropertyDescriptor
konstruktor w pierwszym przykładzie, który przyjmuje metodę dostępunazwy powyżej, ponieważ początkowo nie maMethod
zapisane wSoftReference
s trzyma aktualny getter i setter.
W drugim przykładzie metoda odczytu i metoda zapisu są przechowywane wSoftReference
obiekty wPropertyDescriptor
przez konstruktora i na początku będą one zawierały odniesienia doreadMethod
iwriteMethod
getter i setterMethod
s przekazane konstruktorowi. Jeśli w pewnym momencie te miękkie odnośniki zostaną wyczyszczone tak, jak Garbage Collector może (i zrobi to), togetWriteMethod()
kod zobaczy, żeSoftReference
zwraca wartość null i spróbuje odkryć seter.Tym razem, używając tej samej ścieżki kodu wewnątrzPropertyDescriptor
powoduje to, że pierwszy przykład niepowodzenia w JDK 1.7 spowoduje ustawienie zapisuMethod
donull
ponieważ typ powrotu nie jestvoid
. (Typ powrotu tonie część Javapodpis metody.)
Zmienianie zachowania w miarę upływu czasu podczas używania niestandardowegoBeanInfo
może być bardzo mylące. Próbuję zduplikować warunki, które powodują, że Garbage Collector je usuwaSoftReferences
jest także nudne (choć może drwi z niektórych instrumentów może pomóc.)
WiosnaExtendedBeanInfo
klasa ma testy podobne do powyższych. Oto prawdziwy test jednostkowy Spring 3.1.1 zExtendedBeanInfoTest
który przejdzie w tryb testu jednostki, ale testowany kod nie powiedzie się w podstępnym trybie po 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));
}
Jedną z sugestii jest to, że możemy utrzymać bieżący kod we współpracy z ustawiaczami nie unieważniającymi, uniemożliwiając tylko nieznaczne osiąganie metod ustawiających. Wygląda na to, że zadziała, ale jest to raczej hak wokół zmienionego zachowania w JDK 1.7.
P: Czy istnieje jakaś ostateczna specyfikacja stwierdzająca, że osoby, które nie są puste, powinny być wyklęte? Nic nie znalazłem, a obecnie uważam to za błąd w bibliotekach JDK 1.7. Czy się mylę i dlaczego?