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();
|
||||
this.name = resolveName();
|
||||
if (name != null) {
|
||||
this.name = name;
|
||||
}
|
||||
else {
|
||||
this.name = resolveName();
|
||||
}
|
||||
this.annotations = resolveAnnotations();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue