Dlaczego zachowanie PropertyDescriptor zmieniło się z Java 1.6 na 1.7?

Aktualizacja: Oracle potwierdziło to jako błąd.

Podsumowanie: Pewny zwyczajBeanInfos iPropertyDescriptors 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 zwyczajuBeanInfos, które umożliwiają programową kontrolęPropertyDescriptors 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 wSoftReferences 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 setterMethods 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?

questionAnswers(4)

yourAnswerToTheQuestion