Warum hat sich das Verhalten von PropertyDescriptor von Java 1.6 auf 1.7 geändert?
Update: Oracle hat dies als Fehler bestätigt.
Zusammenfassung: Bestimmter BrauchBeanInfo
s undPropertyDescriptor
s, die in JDK 1.6 funktionieren, schlagen in JDK 1.7 fehl, und einige schlagen erst fehl, nachdem Garbage Collection bestimmte SoftReferences ausgeführt und gelöscht hat.
Bearbeiten: Dies wird auch die brechenExtendedBeanInfo
im Frühjahr 3.1 wie am Ende des Beitrags vermerkt.
Bearbeiten: Wenn Sie Abschnitte 7.1 oder 8.3 der JavaBeans-Spezifikation aufrufen, erläutern Sie genau, wo diese Teile der Spezifikation sindbenötigen etwas. Die Sprache ist in diesen Abschnitten nicht zwingend oder normativ. Die Sprache in diesen Abschnitten ist die von Beispielen, die als Spezifikation bestenfalls mehrdeutig sind. Darüber hinaus ist dieBeanInfo
Die API ermöglicht es einem, das Standardverhalten speziell zu ändern, und dies ist im folgenden zweiten Beispiel deutlich zu erkennen.
Die Java Beans-Spezifikation sucht nach Standard-Setter-Methoden mit dem Rückgabetyp void, ermöglicht jedoch die Anpassung der Getter- und Setter-Methoden über ajava.beans.PropertyDescriptor
. Die einfachste Möglichkeit, es zu verwenden, bestand darin, die Namen des Get- und Setters anzugeben.
new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");
Dies hat in JDK 1.5 und JDK 1.6 funktioniert, um den Namen des Setters anzugeben, auch wenn sein Rückgabetyp nicht ungültig ist, wie im folgenden Testfall:
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;
}
}
Das Beispiel der SitteBeanInfo
s, die die programmatische Steuerung von ermöglichenPropertyDescriptor
s in der Java-Beans-Spezifikation verwenden alle void-Rückgabetypen für ihre Setter, aber nichts in der Spezifikation gibt an, dass diese Beispiele normativ sind, und jetzt hat sich das Verhalten dieses Hilfsprogramms auf niedriger Ebene in den neuen Java-Klassen geändert, was zufällig gescheitert ist ein Code, an dem ich arbeite.
Es gibt zahlreiche Änderungen in derjava.beans
Paket zwischen JDK 1.6 und 1.7, aber das, bei dem dieser Test fehlschlägt, scheint in diesem Unterschied zu sein:
@@ -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) {
Anstatt einfach die Methode mit dem richtigen Namen und den richtigen Parametern zu akzeptieren, wird diePropertyDescriptor
überprüft nun auch den Rückgabetyp, um festzustellen, ob er null ist, sodass der flüssige Setter nicht mehr verwendet wird. DasPropertyDescriptor
wirft einIntrospectionException
in diesem Fall: "Methode nicht gefunden: setI".
Das Problem ist jedoch viel heimtückischer als der oben beschriebene einfache Test. Eine andere Möglichkeit, die Getter- und Setter-Methoden in der anzugebenPropertyDescriptor
für einen BrauchBeanInfo
ist die tatsächliche zu verwendenMethod
Objekte:
@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;
}
Nun der obige Codewerden Führen Sie einen Komponententest sowohl in 1.6 als auch in 1.7 durch. Der Code schlägt jedoch zu einem bestimmten Zeitpunkt während der Lebensdauer der JVM-Instanz aufgrund derselben Änderung fehl, durch die das erste Beispiel sofort fehlschlägt. Im zweiten Beispiel gibt es nur einen Hinweis darauf, dass etwas schief gelaufen ist, wenn Sie versuchen, die benutzerdefinierte Funktion zu verwendenPropertyDescriptor
. Der Setter ist null, und die meisten Utility-Codes bedeuten, dass die Eigenschaft schreibgeschützt ist.
Der Code im Diff ist drinPropertyDescriptor.getWriteMethod()
. Es wird ausgeführt, wenn dieSoftReference
Halten Sie den aktuellen SetterMethod
ist leer. Dieser Code wird von der aufgerufenPropertyDescriptor
Konstruktor im ersten Beispiel, der die Zugriffsmethode verwendetNamen oben, weil es anfangs keine gibtMethod
gespeichert in derSoftReference
s hält den aktuellen Getter und Setter.
Im zweiten Beispiel sind die Lesemethode und die Schreibmethode in gespeichertSoftReference
Objekte in derPropertyDescriptor
vom Konstruktor, und diese enthalten zunächst Verweise auf diereadMethod
undwriteMethod
Getter und SetterMethod
s an den Konstruktor übergeben. Wenn diese weichen Verweise zu einem bestimmten Zeitpunkt gelöscht werden, wie es der Garbage Collector tun darf (und dies auch tun wird), wird dergetWriteMethod()
Code wird sehen, dass dieSoftReference
gibt null zurück, und es wird versucht, den Setter zu finden.Diesmal, mit dem gleichen Code-Pfad im InnerenPropertyDescriptor
Wenn das erste Beispiel in JDK 1.7 fehlschlägt, wird der Schreibvorgang festgelegtMethod
zunull
weil der Rückgabetyp nicht istvoid
. (Der Rückgabetyp istnicht Teil eines JavaMethodensignatur.)
Wenn sich das Verhalten im Laufe der Zeit ändert, wenn Sie eine benutzerdefinierte Einstellung verwendenBeanInfo
kann sehr verwirrend sein. Es wurde versucht, die Bedingungen zu duplizieren, unter denen der Garbage Collector diese Bedingungen löschtSoftReferences
ist auch langweilig (obwohl vielleicht ein paar instrumentelle Verspottungen helfen können.)
Der FrühlingExtendedBeanInfo
Klasse hat ähnliche Tests wie oben. Hier ein aktueller Spring 3.1.1 Unit Test vonExtendedBeanInfoTest
Dies wird im Unit-Test-Modus bestanden, aber der zu testende Code schlägt im Post-GC-Modus fehl:
@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));
}
Ein Vorschlag ist, dass wir den aktuellen Code für die nicht-leeren Setter beibehalten können, indem wir verhindern, dass die Setter-Methoden nur leicht erreichbar sind. Das scheint zu funktionieren, aber das ist eher ein Hack um das geänderte Verhalten in JDK 1.7.
F: Gibt es eine definitive Spezifikation, die besagt, dass nicht leere Setter Anathema sein sollten? Ich habe nichts gefunden und halte dies derzeit für einen Fehler in den JDK 1.7-Bibliotheken. Irre ich mich und warum?