Merge pull request #120 from poutsma/SPR-9677
* SPR-9677: Introduce strategy for BeanInfo creation
This commit is contained in:
		
						commit
						29613f1c21
					
				| 
						 | 
				
			
			@ -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();
 | 
			
		||||
		if (name != null) {
 | 
			
		||||
			this.name = name;
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			this.name = resolveName();
 | 
			
		||||
		}
 | 
			
		||||
		this.annotations = resolveAnnotations();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue