Avoid 'type mismatch' errors in ExtendedBeanInfo
Certain edge cases around return type covariance can trigger an IntrospectionException when trying to create a new PropertyDescriptor; particularly around the addition of write methods with parameter types that do not match read method return types. These type mismatch exceptions are raised during normal Introspector operations as well (i.e. without ExtendedBeanInfo in the mix), but the Introspector intentionally supresses them. In covariance cases, there is often already a suitable write method present, e.g. discovered in a supertype or superinterface, that, with the benefit of bridge methods works just fine in practice at runtime. That is to say, in these suppression cases, the rejection of the write method is 'OK' in that there is already a write method present that can handle a call. ExtendedBeanInfo now mirrors this suppression behavior, but does issue a WARN-level log message to let the user know. An important effect of this change is that ExtendedBeanInfo now modifies the delegate BeanInfo object, whereas previously it did not. In practice this probably matters very little, but it is a design change worth noting. The reason for this change was to avoid the need to create new PropertyDescriptors wherever possible. It was discovered that by updating existing PDs, one can avoid these IntrospectionExceptions a greater percentage of the time. Issue: SPR-8806
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.beans;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
@@ -29,7 +30,9 @@ import java.beans.IndexedPropertyDescriptor;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.ExtendedBeanInfo.PropertyDescriptorComparator;
|
||||
|
||||
@@ -116,11 +119,15 @@ public class ExtendedBeanInfoTests {
|
||||
}
|
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class);
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
|
||||
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
|
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
|
||||
}
|
||||
@@ -134,11 +141,15 @@ public class ExtendedBeanInfoTests {
|
||||
}
|
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class);
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
|
||||
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
|
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
|
||||
|
||||
@@ -161,11 +172,15 @@ public class ExtendedBeanInfoTests {
|
||||
}
|
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class);
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
|
||||
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
|
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
|
||||
}
|
||||
@@ -200,7 +215,6 @@ public class ExtendedBeanInfoTests {
|
||||
assertThat(c.getBar(), is(42));
|
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class);
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
|
||||
@@ -208,6 +222,14 @@ public class ExtendedBeanInfoTests {
|
||||
assertThat(hasReadMethodForProperty(bi, "bar"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "bar"), is(false));
|
||||
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "bar"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "bar"), is(true));
|
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
|
||||
|
||||
@@ -430,13 +452,19 @@ public class ExtendedBeanInfoTests {
|
||||
}
|
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class);
|
||||
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
|
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foos"), is(false));
|
||||
// again as above, standard Inspector picks up non-void return types on indexed write methods by default
|
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true));
|
||||
|
||||
BeanInfo ebi = new ExtendedBeanInfo(Introspector.getBeanInfo(C.class));
|
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(bi, "foos"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foos"), is(true));
|
||||
// again as above, standard Inspector picks up non-void return types on indexed write methods by default
|
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "foos"), is(true));
|
||||
|
||||
assertThat(hasIndexedReadMethodForProperty(ebi, "foos"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(ebi, "foos"), is(true));
|
||||
assertThat(hasIndexedWriteMethodForProperty(ebi, "foos"), is(true));
|
||||
@@ -454,11 +482,15 @@ public class ExtendedBeanInfoTests {
|
||||
}
|
||||
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class);
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));
|
||||
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(bi, "foo"), is(true));
|
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "foo"), is(true));
|
||||
assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
|
||||
|
||||
@@ -514,13 +546,19 @@ public class ExtendedBeanInfoTests {
|
||||
public Object setDateFormat(int dateStyle, int timeStyle) { return new Object(); }
|
||||
}
|
||||
BeanInfo bi = Introspector.getBeanInfo(C.class);
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "dateFormat"), is(false));
|
||||
assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(false));
|
||||
assertThat(hasIndexedReadMethodForProperty(bi, "dateFormat"), is(false));
|
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(true));
|
||||
|
||||
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
|
||||
|
||||
assertThat(hasReadMethodForProperty(bi, "dateFormat"), is(false));
|
||||
assertThat(hasWriteMethodForProperty(bi, "dateFormat"), is(true));
|
||||
assertThat(hasIndexedReadMethodForProperty(bi, "dateFormat"), is(false));
|
||||
assertThat(hasIndexedWriteMethodForProperty(bi, "dateFormat"), is(true));
|
||||
|
||||
assertThat(hasReadMethodForProperty(ebi, "dateFormat"), is(false));
|
||||
assertThat(hasWriteMethodForProperty(ebi, "dateFormat"), is(true));
|
||||
assertThat(hasIndexedReadMethodForProperty(ebi, "dateFormat"), is(false));
|
||||
@@ -625,4 +663,75 @@ public class ExtendedBeanInfoTests {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reproSpr8806_y() throws IntrospectionException, SecurityException, NoSuchMethodException {
|
||||
Introspector.getBeanInfo(LawLibrary.class);
|
||||
}
|
||||
|
||||
@Ignore @Test
|
||||
public void reproSpr8806_x() throws IntrospectionException, SecurityException, NoSuchMethodException {
|
||||
BeanInfo info = Introspector.getBeanInfo(LawLibrary.class);
|
||||
for (PropertyDescriptor d : info.getPropertyDescriptors()) {
|
||||
if (d.getName().equals("book")) {
|
||||
Method readMethod = d.getReadMethod();
|
||||
Method writeMethod = d.getWriteMethod();
|
||||
System.out.println(format("READ : %s.%s (bridge:%s)",
|
||||
readMethod.getDeclaringClass().getSimpleName(), readMethod.getName(), readMethod.isBridge()));
|
||||
System.out.println(format("WRITE: %s.%s (bridge:%s)",
|
||||
writeMethod.getDeclaringClass().getSimpleName(), writeMethod.getName(), writeMethod.isBridge()));
|
||||
new PropertyDescriptor("book", readMethod, writeMethod);
|
||||
}
|
||||
}
|
||||
|
||||
Method readMethod = LawLibrary.class.getMethod("getBook");
|
||||
Method writeMethod = LawLibrary.class.getMethod("setBook", Book.class);
|
||||
|
||||
System.out.println(format("read : %s.%s (bridge:%s)",
|
||||
readMethod.getDeclaringClass().getSimpleName(), readMethod.getName(), readMethod.isBridge()));
|
||||
System.out.println(format("write: %s.%s (bridge:%s)",
|
||||
writeMethod.getDeclaringClass().getSimpleName(), writeMethod.getName(), writeMethod.isBridge()));
|
||||
|
||||
|
||||
System.out.println("--------");
|
||||
for (Method m : LawLibrary.class.getMethods()) {
|
||||
if (m.getDeclaringClass() == Object.class) continue;
|
||||
System.out.println(format("%s %s.%s(%s) [bridge:%s]",
|
||||
m.getReturnType().getSimpleName(), m.getDeclaringClass().getSimpleName(),
|
||||
m.getName(),
|
||||
m.getParameterTypes().length == 1 ? m.getParameterTypes()[0].getSimpleName() : "",
|
||||
m.isBridge()));
|
||||
}
|
||||
|
||||
//new ExtendedBeanInfo(info);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reproSpr8806() throws IntrospectionException {
|
||||
BeanInfo bi = Introspector.getBeanInfo(LawLibrary.class);
|
||||
new ExtendedBeanInfo(bi); // throws
|
||||
}
|
||||
|
||||
interface Book { }
|
||||
|
||||
interface TextBook extends Book { }
|
||||
|
||||
interface LawBook extends TextBook { }
|
||||
|
||||
interface BookOperations {
|
||||
Book getBook();
|
||||
void setBook(Book book);
|
||||
}
|
||||
|
||||
interface TextBookOperations extends BookOperations {
|
||||
TextBook getBook();
|
||||
}
|
||||
|
||||
abstract class Library {
|
||||
public Book getBook() { return null; }
|
||||
public void setBook(Book book) { }
|
||||
}
|
||||
|
||||
class LawLibrary extends Library implements TextBookOperations {
|
||||
public LawBook getBook() { return null; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user