Browse Source

Backport "Use ExtendedBeanInfo on an as-needed basis only"

Prior to this change, CachedIntrospectionResults delegated to
ExtendedBeanInfo by default in order to inspect JavaBean
PropertyDescriptor information for bean classes.

Originally introduced with SPR-8079, ExtendedBeanInfo was designed to
go beyond the capabilities of the default JavaBeans Introspector in
order to support non-void returning setter methods, principally to
support use of builder-style APIs within Spring XML. This is a complex
affair, and the non-trivial logic in ExtendedBeanInfo has led to various
bugs including regressions for bean classes that do not declare
non-void returning setters.

Many of these issues were fixed when overhauling non-void JavaBean write
method support in SPR-10029, however it appears that some extremely
subtle issues may still remain. ExtendedBeanInfo was taken out of use
completely in the 3.2.x line with SPR-9677 and the new BeanInfoFactory
mechanism. This support was not backported to 3.1.x, however, in the
interest of stability.

This commit, then, inlines the conditional logic introduced by
BeanInfoFactory directly into CachedIntrospectionResults. The net effect
is that ExtendedBeanInfo is out of the code path entirely except for
bean classes that actually contain 'candidate' methods, i.e. non-void
and/or static write methods.

This commit also includes the changes made in SPR-10115.

Issue: SPR-9723, SPR-10029, SPR-9677, SPR-8079
3.1.x
Chris Beams 13 years ago
parent
commit
a3161632dd
  1. 12
      org.springframework.beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
  2. 23
      org.springframework.beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java
  3. 18
      org.springframework.beans/src/test/java/org/springframework/beans/BeanWrapperTests.java
  4. 23
      org.springframework.beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java

12
org.springframework.beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java

@ -22,6 +22,7 @@ import java.beans.Introspector; @@ -22,6 +22,7 @@ import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
@ -32,7 +33,6 @@ import java.util.WeakHashMap; @@ -32,7 +33,6 @@ import java.util.WeakHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@ -214,7 +214,15 @@ public class CachedIntrospectionResults { @@ -214,7 +214,15 @@ public class CachedIntrospectionResults {
if (logger.isTraceEnabled()) {
logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
}
this.beanInfo = new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass));
BeanInfo originalBeanInfo = Introspector.getBeanInfo(beanClass);
BeanInfo extendedBeanInfo = null;
for (Method method : beanClass.getMethods()) {
if (ExtendedBeanInfo.isCandidateWriteMethod(method)) {
extendedBeanInfo = new ExtendedBeanInfo(originalBeanInfo);
break;
}
}
this.beanInfo = extendedBeanInfo != null ? extendedBeanInfo : originalBeanInfo;
// Immediately remove class from Introspector cache, to allow for proper
// garbage collection on class loader shutdown - we cache it here anyway,

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -42,8 +42,8 @@ import static org.springframework.beans.PropertyDescriptorUtils.*; @@ -42,8 +42,8 @@ import static org.springframework.beans.PropertyDescriptorUtils.*;
/**
* Decorator for a standard {@link BeanInfo} object, e.g. as created by
* {@link Introspector#getBeanInfo(Class)}, designed to discover and register non-void
* returning setter methods. For example:
* {@link Introspector#getBeanInfo(Class)}, designed to discover and register static
* and/or non-void returning setter methods. For example:
* <pre>{@code
* public class Bean {
* private Foo foo;
@ -102,17 +102,17 @@ class ExtendedBeanInfo implements BeanInfo { @@ -102,17 +102,17 @@ class ExtendedBeanInfo implements BeanInfo {
new SimpleNonIndexedPropertyDescriptor(pd));
}
for (Method method : findNonVoidWriteMethods(delegate.getMethodDescriptors())) {
handleNonVoidWriteMethod(method);
for (Method method : findCandidateWriteMethods(delegate.getMethodDescriptors())) {
handleCandidateWriteMethod(method);
}
}
private List<Method> findNonVoidWriteMethods(MethodDescriptor[] methodDescriptors) {
private List<Method> findCandidateWriteMethods(MethodDescriptor[] methodDescriptors) {
List<Method> matches = new ArrayList<Method>();
for (MethodDescriptor methodDescriptor : methodDescriptors) {
Method method = methodDescriptor.getMethod();
if (isNonVoidWriteMethod(method)) {
if (isCandidateWriteMethod(method)) {
matches.add(method);
}
}
@ -127,20 +127,23 @@ class ExtendedBeanInfo implements BeanInfo { @@ -127,20 +127,23 @@ class ExtendedBeanInfo implements BeanInfo {
return matches;
}
public static boolean isNonVoidWriteMethod(Method method) {
public static boolean isCandidateWriteMethod(Method method) {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
int nParams = parameterTypes.length;
if (methodName.length() > 3 && methodName.startsWith("set") &&
Modifier.isPublic(method.getModifiers()) &&
!void.class.isAssignableFrom(method.getReturnType()) &&
(
!void.class.isAssignableFrom(method.getReturnType()) ||
Modifier.isStatic(method.getModifiers())
) &&
(nParams == 1 || (nParams == 2 && parameterTypes[0].equals(int.class)))) {
return true;
}
return false;
}
private void handleNonVoidWriteMethod(Method method) throws IntrospectionException {
private void handleCandidateWriteMethod(Method method) throws IntrospectionException {
int nParams = method.getParameterTypes().length;
String propertyName = propertyNameFor(method);
Class<?> propertyType = method.getParameterTypes()[nParams-1];

18
org.springframework.beans/src/test/java/org/springframework/beans/BeanWrapperTests.java

@ -1541,6 +1541,24 @@ public final class BeanWrapperTests { @@ -1541,6 +1541,24 @@ public final class BeanWrapperTests {
assertEquals(TestEnum.TEST_VALUE, consumer.getEnumValue());
}
@Test
public void cornerSpr10115() {
Spr10115Bean foo = new Spr10115Bean();
BeanWrapperImpl bwi = new BeanWrapperImpl();
bwi.setWrappedInstance(foo);
bwi.setPropertyValue("prop1", "val1");
assertEquals("val1", Spr10115Bean.prop1);
}
static class Spr10115Bean {
private static String prop1;
public static void setProp1(String prop1) {
Spr10115Bean.prop1 = prop1;
}
}
private static class Foo {

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

@ -959,4 +959,27 @@ public class ExtendedBeanInfoTests { @@ -959,4 +959,27 @@ public class ExtendedBeanInfoTests {
}
}
@Test
public void shouldSupportStaticWriteMethod() throws IntrospectionException {
{
BeanInfo bi = Introspector.getBeanInfo(WithStaticWriteMethod.class);
assertThat(hasReadMethodForProperty(bi, "prop1"), is(false));
assertThat(hasWriteMethodForProperty(bi, "prop1"), is(false));
assertThat(hasIndexedReadMethodForProperty(bi, "prop1"), is(false));
assertThat(hasIndexedWriteMethodForProperty(bi, "prop1"), is(false));
}
{
BeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(WithStaticWriteMethod.class));
assertThat(hasReadMethodForProperty(bi, "prop1"), is(false));
assertThat(hasWriteMethodForProperty(bi, "prop1"), is(true));
assertThat(hasIndexedReadMethodForProperty(bi, "prop1"), is(false));
assertThat(hasIndexedWriteMethodForProperty(bi, "prop1"), is(false));
}
}
static class WithStaticWriteMethod {
@SuppressWarnings("unused")
public static void setProp1(String prop1) {
}
}
}

Loading…
Cancel
Save