Merge pull request #120 from poutsma/SPR-9677

* SPR-9677:
  Introduce strategy for BeanInfo creation
master
Chris Beams 12 years ago
commit 29613f1c21
  1. 47
      spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java
  2. 4
      spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java
  3. 91
      spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
  4. 21
      spring-beans/src/test/java/org/springframework/beans/CachedIntrospectionResultsTests.java
  5. 41
      spring-beans/src/test/java/org/springframework/beans/DummyBeanInfoFactory.java
  6. 3
      spring-beans/src/test/resources/META-INF/spring.beanInfoFactories
  7. 11
      spring-core/src/main/java/org/springframework/core/convert/Property.java

@ -0,0 +1,47 @@
/*
* Copyright 2002-2012 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
/**
* Strategy for creating {@link BeanInfo} instances.
*
* @author Arjen Poutsma
* @since 3.2
*/
public interface BeanInfoFactory {
/**
* Indicates whether a bean with the given class is supported by this factory.
*
* @param beanClass the bean class
* @return {@code true} if supported; {@code false} otherwise
*/
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
*/
BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException;
}

@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 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.
@ -518,7 +518,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
private Property property(PropertyDescriptor pd) {
GenericTypeAwarePropertyDescriptor typeAware = (GenericTypeAwarePropertyDescriptor) pd;
return new Property(typeAware.getBeanClass(), typeAware.getReadMethod(), typeAware.getWriteMethod());
return new Property(typeAware.getBeanClass(), typeAware.getReadMethod(), typeAware.getWriteMethod(), typeAware.getName());
}

@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2012 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.
@ -20,19 +20,25 @@ import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@ -58,6 +64,12 @@ import org.springframework.util.StringUtils;
*/
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);
/**
@ -73,6 +85,11 @@ public class CachedIntrospectionResults {
*/
static final Map<Class, Object> classCache = Collections.synchronizedMap(new WeakHashMap<Class, Object>());
/** Stores the BeanInfoFactory instances */
private static List<BeanInfoFactory> beanInfoFactories;
private static final Object beanInfoFactoriesMutex = new Object();
/**
* Accept the given ClassLoader as cache-safe, even if its classes would
@ -221,7 +238,20 @@ public class CachedIntrospectionResults {
if (logger.isTraceEnabled()) {
logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
}
this.beanInfo = new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass));
BeanInfo beanInfo = null;
List<BeanInfoFactory> beanInfoFactories = getBeanInfoFactories(beanClass.getClassLoader());
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
if (beanInfoFactory.supports(beanClass)) {
beanInfo = beanInfoFactory.getBeanInfo(beanClass);
break;
}
}
if (beanInfo == null) {
// If none of the factories supported the class, use the default
beanInfo = new ExtendedBeanInfo(Introspector.getBeanInfo(beanClass));
}
this.beanInfo = beanInfo;
// Immediately remove class from Introspector cache, to allow for proper
// garbage collection on class loader shutdown - we cache it here anyway,
@ -305,4 +335,61 @@ public class CachedIntrospectionResults {
}
}
private static List<BeanInfoFactory> getBeanInfoFactories(ClassLoader classLoader) {
if (beanInfoFactories == null) {
synchronized (beanInfoFactoriesMutex) {
if (beanInfoFactories == null) {
try {
Properties properties =
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;
}
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);
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2012 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.
@ -16,21 +16,24 @@
package org.springframework.beans;
import static org.junit.Assert.*;
import java.beans.BeanInfo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.core.OverridingClassLoader;
import test.beans.TestBean;
import org.springframework.core.OverridingClassLoader;
/**
* @author Juergen Hoeller
* @author Chris Beams
* @author Arjen Poutsma
*/
public final class CachedIntrospectionResultsTests {
@Test
public void testAcceptClassLoader() throws Exception {
public void acceptClassLoader() throws Exception {
BeanWrapper bw = new BeanWrapperImpl(TestBean.class);
assertTrue(bw.isWritableProperty("name"));
assertTrue(bw.isWritableProperty("age"));
@ -50,4 +53,12 @@ public final class CachedIntrospectionResultsTests {
assertTrue(CachedIntrospectionResults.classCache.containsKey(TestBean.class));
}
@Test
public void customBeanInfoFactory() throws Exception {
CachedIntrospectionResults results = CachedIntrospectionResults.forClass(CachedIntrospectionResultsTests.class);
BeanInfo beanInfo = results.getBeanInfo();
assertTrue("Invalid BeanInfo instance", beanInfo instanceof DummyBeanInfoFactory.DummyBeanInfo);
}
}

@ -0,0 +1,41 @@
/*
* Copyright 2002-2012 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans;
import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;
import java.beans.SimpleBeanInfo;
public class DummyBeanInfoFactory implements BeanInfoFactory {
public boolean supports(Class<?> beanClass) {
return CachedIntrospectionResultsTests.class.equals(beanClass);
}
public BeanInfo getBeanInfo(Class<?> beanClass) {
return new DummyBeanInfo();
}
public static class DummyBeanInfo extends SimpleBeanInfo {
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return new PropertyDescriptor[0];
}
}
}

@ -0,0 +1,3 @@
# Dummy bean info factories file, used by CachedIntrospectionResultsTests
org.springframework.beans.DummyBeanInfoFactory

@ -57,11 +57,20 @@ public final class Property {
public Property(Class<?> objectType, Method readMethod, Method writeMethod) {
this(objectType, readMethod, writeMethod, null);
}
public Property(Class<?> objectType, Method readMethod, Method writeMethod, String name) {
this.objectType = objectType;
this.readMethod = readMethod;
this.writeMethod = writeMethod;
this.methodParameter = resolveMethodParameter();
this.name = resolveName();
if (name != null) {
this.name = name;
}
else {
this.name = resolveName();
}
this.annotations = resolveAnnotations();
}

Loading…
Cancel
Save