Description
Petar Tahchiev opened SPR-16978 and commented
Hello,
consider the following setup:
// code from spring-security
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
public interface MyUserDetails extends UserDetails {
Locale getLocale();
void setLocale(final Locale locale);
/* setters for super properties */
void setUsername(String username);
void setPassword(String password);
}
public interface CustomerDetails extends MyUsersDetails {
String getPhone();
void setPhone(String phone);
}
Now invoke BeanUtils.getPropertyDescriptor(CustomerDetails.class, "username");
and you will see that the returned PropertyDescriptor
has correct writeMethod
but the readMethod
is null. As a result, if I try to submit a CustomerDetails
form in spring-mvc
I get this error:
2018-06-26 18:13:33,604 org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/storefront].[dispatcherServlet] [https-jsse-nio-127.0.0.1-8112-exec-5] ERROR: Servlet.service() for servlet [dispatcherServlet] in context with path [/storefront] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: Method must not be null] with root cause
java.lang.IllegalArgumentException: Method must not be null
at org.springframework.util.Assert.notNull(Assert.java:193)
at org.springframework.core.MethodParameter.<init>(MethodParameter.java:120)
at org.springframework.core.MethodParameter.<init>(MethodParameter.java:106)
at org.springframework.data.web.MapDataBinder$MapPropertyAccessor.setPropertyValue(MapDataBinder.java:193)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValue(AbstractPropertyAccessor.java:67)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:97)
at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:839)
at org.springframework.validation.DataBinder.doBind(DataBinder.java:735)
at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:197)
at org.springframework.validation.DataBinder.bind(DataBinder.java:720
I believe this is because in CachedIntrospectionResults
a look goes through all super interfaces:
while (clazz != null && clazz != Object.class) {
Class<?>[] ifcs = clazz.getInterfaces();
for (Class<?> ifc : ifcs) {
if (!ClassUtils.isJavaLanguageInterface(ifc)) {
for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) {
until Object
is reached and for each finds the property descriptor. However when it reaches MyUserDetails
it finds the setter method and creates a PropertyDescriptor
with null read method which is wrong because the read method is defined in the parent interface.
Affects: 5.0.7
Issue Links:
- Java 8 default methods not detected as bean properties [SPR-14198] #18772 Java 8 default methods not detected as bean properties
- CachedIntrospectionResults should use BeanInfoFactory when introspecting implemented interfaces [SPR-16322] #20869 CachedIntrospectionResults should use BeanInfoFactory when introspecting implemented interfaces
- Consider caching interface-derived BeanInfo instances in CachedIntrospectionResults [SPR-16568] #21110 Consider caching interface-derived BeanInfo instances in CachedIntrospectionResults
Referenced from: commits bf5fe46