Skip to content

Commit f1ecd1c

Browse files
committed
Detect overridden boolean getters in ExtendedBeanInfo
Prior to this commit, and due to idiosyncracies of java.beans.Introspector, overridden boolean getter methods were not detected by Spring's ExtendedBeanInfo, which relied on too-strict Method equality checks when selecting read and write methods. Now ExtendedBeanInfo uses Spring's ClassUtils#getMostSpecificMethod against the methods returned from java.beans.Introspector in order to ensure that subsequent equality checks are reasonable to make. Issue: SPR-8949
1 parent c1df51a commit f1ecd1c

File tree

2 files changed

+63
-8
lines changed

2 files changed

+63
-8
lines changed

org.springframework.beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
3535
import org.apache.commons.logging.Log;
3636
import org.apache.commons.logging.LogFactory;
3737
import org.springframework.util.Assert;
38+
import org.springframework.util.ClassUtils;
3839
import org.springframework.util.ReflectionUtils;
3940
import org.springframework.util.StringUtils;
4041

@@ -159,24 +160,27 @@ public ExtendedBeanInfo(BeanInfo delegate) throws IntrospectionException {
159160
// the method is not a setter, but is it a getter?
160161
for (PropertyDescriptor pd : delegate.getPropertyDescriptors()) {
161162
// have we already copied this read method to a property descriptor locally?
163+
String propertyName = pd.getName();
164+
Method readMethod = pd.getReadMethod();
165+
Method mostSpecificReadMethod = ClassUtils.getMostSpecificMethod(readMethod, method.getDeclaringClass());
162166
for (PropertyDescriptor existingPD : this.propertyDescriptors) {
163-
if (method.equals(pd.getReadMethod())
164-
&& existingPD.getName().equals(pd.getName())) {
167+
if (method.equals(mostSpecificReadMethod)
168+
&& existingPD.getName().equals(propertyName)) {
165169
if (existingPD.getReadMethod() == null) {
166170
// no -> add it now
167-
this.addOrUpdatePropertyDescriptor(pd, pd.getName(), method, pd.getWriteMethod());
171+
this.addOrUpdatePropertyDescriptor(pd, propertyName, method, pd.getWriteMethod());
168172
}
169173
// yes -> do not add a duplicate
170174
continue ALL_METHODS;
171175
}
172176
}
173-
if (method.equals(pd.getReadMethod())
177+
if (method.equals(mostSpecificReadMethod)
174178
|| (pd instanceof IndexedPropertyDescriptor && method.equals(((IndexedPropertyDescriptor) pd).getIndexedReadMethod()))) {
175179
// yes -> copy it, including corresponding setter method (if any -- may be null)
176180
if (pd instanceof IndexedPropertyDescriptor) {
177-
this.addOrUpdatePropertyDescriptor(pd, pd.getName(), pd.getReadMethod(), pd.getWriteMethod(), ((IndexedPropertyDescriptor)pd).getIndexedReadMethod(), ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod());
181+
this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, pd.getWriteMethod(), ((IndexedPropertyDescriptor)pd).getIndexedReadMethod(), ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod());
178182
} else {
179-
this.addOrUpdatePropertyDescriptor(pd, pd.getName(), pd.getReadMethod(), pd.getWriteMethod());
183+
this.addOrUpdatePropertyDescriptor(pd, propertyName, readMethod, pd.getWriteMethod());
180184
}
181185
continue ALL_METHODS;
182186
}

org.springframework.beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@
2222
import static org.hamcrest.Matchers.greaterThan;
2323
import static org.hamcrest.Matchers.lessThan;
2424
import static org.junit.Assert.assertThat;
25+
import static org.junit.Assert.assertTrue;
2526
import static org.junit.Assert.fail;
2627

2728
import java.beans.BeanInfo;
2829
import java.beans.IndexedPropertyDescriptor;
2930
import java.beans.IntrospectionException;
3031
import java.beans.Introspector;
3132
import java.beans.PropertyDescriptor;
33+
import java.lang.reflect.Method;
3234

3335
import org.junit.Test;
3436
import org.springframework.beans.ExtendedBeanInfo.PropertyDescriptorComparator;
37+
import org.springframework.util.ClassUtils;
3538

3639
import test.beans.TestBean;
3740

@@ -660,13 +663,14 @@ private boolean hasIndexedReadMethodForProperty(BeanInfo beanInfo, String proper
660663
return false;
661664
}
662665

666+
663667
@Test
664668
public void reproSpr8806() throws IntrospectionException {
665669
// does not throw
666670
Introspector.getBeanInfo(LawLibrary.class);
667671

668672
// does not throw after the changes introduced in SPR-8806
669-
new ExtendedBeanInfo(Introspector.getBeanInfo(LawLibrary.class));
673+
new ExtendedBeanInfo(Introspector.getBeanInfo(LawLibrary.class));
670674
}
671675

672676
interface Book { }
@@ -692,4 +696,51 @@ public void setBook(Book book) { }
692696
class LawLibrary extends Library implements TextBookOperations {
693697
public LawBook getBook() { return null; }
694698
}
699+
700+
701+
/**
702+
* java.beans.Introspector returns the "wrong" declaring class for overridden read
703+
* methods, which in turn violates expectations in {@link ExtendedBeanInfo} regarding
704+
* method equality. Spring's {@link ClassUtils#getMostSpecificMethod(Method, Class)}
705+
* helps out here, and is now put into use in ExtendedBeanInfo as well
706+
*/
707+
@Test
708+
public void demonstrateCauseSpr8949() throws IntrospectionException {
709+
BeanInfo info = Introspector.getBeanInfo(B.class);
710+
711+
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
712+
if ("targetMethod".equals(pd.getName())) {
713+
Method readMethod = pd.getReadMethod();
714+
assertTrue(readMethod.getDeclaringClass().equals(A.class)); // we expected B!
715+
716+
Method msReadMethod = ClassUtils.getMostSpecificMethod(readMethod, B.class);
717+
assertTrue(msReadMethod.getDeclaringClass().equals(B.class)); // and now we get it.
718+
}
719+
}
720+
}
721+
722+
@Test
723+
public void cornerSpr8949() throws IntrospectionException {
724+
BeanInfo bi = Introspector.getBeanInfo(B.class);
725+
ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);
726+
727+
assertThat(hasReadMethodForProperty(bi, "targetMethod"), is(true));
728+
assertThat(hasWriteMethodForProperty(bi, "targetMethod"), is(false));
729+
730+
assertThat(hasReadMethodForProperty(ebi, "targetMethod"), is(true));
731+
assertThat(hasWriteMethodForProperty(ebi, "targetMethod"), is(false));
732+
}
733+
734+
static class A {
735+
public boolean isTargetMethod() {
736+
return false;
737+
}
738+
}
739+
740+
static class B extends A {
741+
@Override
742+
public boolean isTargetMethod() {
743+
return false;
744+
}
745+
}
695746
}

0 commit comments

Comments
 (0)