Refactor BeanInfoFactory

This commit refactors the BeanInfoFactory so that:

 - supports() and getBeanInfo() are folded into one, so that getBeanInfo()
    returns null if a given class is not supported.
 - CachedIntrospectionResults now uses SpringFactoriesLoader
This commit is contained in:
Arjen Poutsma 2012-09-10 14:28:53 +02:00
parent aeff91c1da
commit 82739dd4ac
6 changed files with 38 additions and 100 deletions

View File

@ -23,17 +23,18 @@ import java.beans.IntrospectionException;
* Strategy for creating {@link BeanInfo} instances. * Strategy for creating {@link BeanInfo} instances.
* *
* <p>BeanInfoFactories are are instantiated by the {@link CachedIntrospectionResults}, * <p>BeanInfoFactories are are instantiated by the {@link CachedIntrospectionResults},
* which looks for {@code META-INF/spring.beanInfoFactories} files on the class path. * by using the {@link org.springframework.core.io.support.SpringFactoriesLoader} utility
* These files contain one or more {@code BeanInfoFactory} class names, each of a single * class.
* line. When a {@link BeanInfo} is to be created, the {@code CachedIntrospectionResults}
* will iterate through the discovered factories, asking each one if it {@linkplain
* #supports(Class) supports} the given bean class. If it does, {@link
* #getBeanInfo(Class)} will be called; if not, the next factory will be queried. If none
* of the factories support the class, an standard {@link BeanInfo} is created as a
* default.
* *
* <p>Note that the {@link CachedIntrospectionResults} sorts the {@code BeanInfoFactory} * When a {@link BeanInfo} is to be created, the {@code CachedIntrospectionResults}
* instances by {@link org.springframework.core.annotation.Order Order}, so that ones with * will iterate through the discovered factories, calling {@link
* #getBeanInfo(Class)} on each one. If {@code null} is returned, the next factory will
* be queried. If none of the factories support the class, an standard {@link BeanInfo}
* is created as a default.
*
* <p>Note that the {@link org.springframework.core.io.support.SpringFactoriesLoader}
* sorts the {@code BeanInfoFactory} instances by
* {@link org.springframework.core.annotation.Order @Order}, so that ones with
* a higher precedence come first. * a higher precedence come first.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
@ -42,18 +43,10 @@ import java.beans.IntrospectionException;
public interface BeanInfoFactory { public interface BeanInfoFactory {
/** /**
* Indicates whether a bean with the given class is supported by this factory. * Returns the bean info for the given class, if supported.
* *
* @param beanClass the bean class * @param beanClass the bean class
* @return {@code true} if supported; {@code false} otherwise * @return the bean info, or {@code null} if not the given class is not supported
*/
boolean supports(Class<?> beanClass);
/**
* Returns the bean info for the given class.
*
* @param beanClass the bean class
* @return the bean info
* @throws IntrospectionException in case of exceptions * @throws IntrospectionException in case of exceptions
*/ */
BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException; BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException;

View File

@ -20,25 +20,21 @@ import java.beans.BeanInfo;
import java.beans.IntrospectionException; import java.beans.IntrospectionException;
import java.beans.Introspector; import java.beans.Introspector;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -64,12 +60,6 @@ import org.springframework.util.StringUtils;
*/ */
public class CachedIntrospectionResults { public class CachedIntrospectionResults {
/**
* The location to look for the bean info mapping files. Can be present in multiple JAR files.
*/
public static final String BEAN_INFO_FACTORIES_LOCATION = "META-INF/spring.beanInfoFactories";
private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class); private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class);
/** /**
@ -242,8 +232,8 @@ public class CachedIntrospectionResults {
BeanInfo beanInfo = null; BeanInfo beanInfo = null;
List<BeanInfoFactory> beanInfoFactories = getBeanInfoFactories(beanClass.getClassLoader()); List<BeanInfoFactory> beanInfoFactories = getBeanInfoFactories(beanClass.getClassLoader());
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) { for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
if (beanInfoFactory.supports(beanClass)) {
beanInfo = beanInfoFactory.getBeanInfo(beanClass); beanInfo = beanInfoFactory.getBeanInfo(beanClass);
if (beanInfo != null) {
break; break;
} }
} }
@ -339,57 +329,12 @@ public class CachedIntrospectionResults {
if (beanInfoFactories == null) { if (beanInfoFactories == null) {
synchronized (beanInfoFactoriesMutex) { synchronized (beanInfoFactoriesMutex) {
if (beanInfoFactories == null) { if (beanInfoFactories == null) {
try { beanInfoFactories = Collections.synchronizedList(SpringFactoriesLoader
Properties properties = .loadFactories(BeanInfoFactory.class, classLoader));
PropertiesLoaderUtils.loadAllProperties(
BEAN_INFO_FACTORIES_LOCATION, classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded BeanInfoFactories: " + properties.keySet());
}
List<BeanInfoFactory> factories = new ArrayList<BeanInfoFactory>(properties.size());
for (Object key : properties.keySet()) {
if (key instanceof String) {
String className = (String) key;
BeanInfoFactory factory = instantiateBeanInfoFactory(className, classLoader);
factories.add(factory);
}
}
Collections.sort(factories, new AnnotationAwareOrderComparator());
beanInfoFactories = Collections.synchronizedList(factories);
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load BeanInfoFactories from location [" + BEAN_INFO_FACTORIES_LOCATION + "]", ex);
}
} }
} }
} }
return beanInfoFactories; return beanInfoFactories;
} }
private static BeanInfoFactory instantiateBeanInfoFactory(String className,
ClassLoader classLoader) {
try {
Class<?> factoryClass = ClassUtils.forName(className, classLoader);
if (!BeanInfoFactory.class.isAssignableFrom(factoryClass)) {
throw new FatalBeanException(
"Class [" + className + "] does not implement the [" +
BeanInfoFactory.class.getName() + "] interface");
}
return (BeanInfoFactory) BeanUtils.instantiate(factoryClass);
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException(
"BeanInfoFactory class [" + className + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid BeanInfoFactory class [" + className +
"]: problem with handler class file or dependent class", err);
}
}
} }

View File

@ -19,7 +19,6 @@ package org.springframework.beans;
import java.beans.BeanInfo; import java.beans.BeanInfo;
import java.beans.IntrospectionException; import java.beans.IntrospectionException;
import java.beans.Introspector; import java.beans.Introspector;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
@ -37,13 +36,21 @@ import org.springframework.core.Ordered;
* @since 3.2 * @since 3.2
* @see BeanInfoFactory * @see BeanInfoFactory
*/ */
class ExtendedBeanInfoFactory implements Ordered, BeanInfoFactory { public class ExtendedBeanInfoFactory implements Ordered, BeanInfoFactory {
/**
* Return a new {@link ExtendedBeanInfo} for the given bean class.
*/
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
return supports(beanClass) ?
new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass)) : null;
}
/** /**
* Return whether the given bean class declares or inherits any non-void returning * Return whether the given bean class declares or inherits any non-void returning
* JavaBeans or <em>indexed property</em> setter methods. * JavaBeans or <em>indexed property</em> setter methods.
*/ */
public boolean supports(Class<?> beanClass) { private boolean supports(Class<?> beanClass) {
for (Method method : beanClass.getMethods()) { for (Method method : beanClass.getMethods()) {
String methodName = method.getName(); String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes(); Class<?>[] parameterTypes = method.getParameterTypes();
@ -59,13 +66,6 @@ class ExtendedBeanInfoFactory implements Ordered, BeanInfoFactory {
return false; return false;
} }
/**
* Return a new {@link ExtendedBeanInfo} for the given bean class.
*/
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
return new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass));
}
public int getOrder() { public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; return Ordered.LOWEST_PRECEDENCE;
} }

View File

@ -1 +0,0 @@
org.springframework.beans.ExtendedBeanInfoFactory

View File

@ -0,0 +1 @@
org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory

View File

@ -18,11 +18,11 @@ package org.springframework.beans;
import java.beans.IntrospectionException; import java.beans.IntrospectionException;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/** /**
* Unit tests for {@link ExtendedBeanInfoTests}. * Unit tests for {@link ExtendedBeanInfoTests}.
* *
@ -38,7 +38,7 @@ public class ExtendedBeanInfoFactoryTests {
class C { class C {
public void setFoo(String s) { } public void setFoo(String s) { }
} }
assertThat(factory.supports(C.class), is(false)); assertThat(factory.getBeanInfo(C.class), nullValue());
} }
@Test @Test
@ -47,7 +47,7 @@ public class ExtendedBeanInfoFactoryTests {
class C { class C {
public C setFoo(String s) { return this; } public C setFoo(String s) { return this; }
} }
assertThat(factory.supports(C.class), is(true)); assertThat(factory.getBeanInfo(C.class), notNullValue());
} }
@Test @Test
@ -56,7 +56,7 @@ public class ExtendedBeanInfoFactoryTests {
class C { class C {
public C setFoo(int i, String s) { return this; } public C setFoo(int i, String s) { return this; }
} }
assertThat(factory.supports(C.class), is(true)); assertThat(factory.getBeanInfo(C.class), notNullValue());
} }
@Test @Test
@ -65,7 +65,7 @@ public class ExtendedBeanInfoFactoryTests {
class C { class C {
void setBar(String s) { } void setBar(String s) { }
} }
assertThat(factory.supports(C.class), is(false)); assertThat(factory.getBeanInfo(C.class), nullValue());
} }
@Test @Test
@ -74,7 +74,7 @@ public class ExtendedBeanInfoFactoryTests {
class C { class C {
C setBar() { return this; } C setBar() { return this; }
} }
assertThat(factory.supports(C.class), is(false)); assertThat(factory.getBeanInfo(C.class), nullValue());
} }
@Test @Test
@ -83,7 +83,7 @@ public class ExtendedBeanInfoFactoryTests {
class C { class C {
C set(String s) { return this; } C set(String s) { return this; }
} }
assertThat(factory.supports(C.class), is(false)); assertThat(factory.getBeanInfo(C.class), nullValue());
} }
} }