Browse Source
Update OnBeanCondition to use a new BeanTypeRegistry which includes optimized code when using a DefaultListableBeanFactory. The optimized version calculates bean types only once per bean and caches the result. Prior to this change the sample "pet clinic" application would spend 400-500 milliseconds evaluating OnBeanConditions, after this change it spends around 120 milliseconds. Fixes gh-1803pull/1815/head
2 changed files with 294 additions and 89 deletions
@ -0,0 +1,285 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2012-2014 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.boot.autoconfigure.condition; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanFactory; |
||||||
|
import org.springframework.beans.factory.BeanFactoryUtils; |
||||||
|
import org.springframework.beans.factory.FactoryBean; |
||||||
|
import org.springframework.beans.factory.ListableBeanFactory; |
||||||
|
import org.springframework.beans.factory.config.BeanDefinition; |
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||||
|
import org.springframework.context.ApplicationListener; |
||||||
|
import org.springframework.context.event.ContextRefreshedEvent; |
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.util.ClassUtils; |
||||||
|
import org.springframework.util.ReflectionUtils; |
||||||
|
import org.springframework.util.StringUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* A registry of the bean types that are contained in a {@link ListableBeanFactory}. |
||||||
|
* Provides similar functionality to |
||||||
|
* {@link ListableBeanFactory#getBeanNamesForType(Class, boolean, boolean)} but is |
||||||
|
* optimized for use by {@link OnBeanCondition} based on the following assumptions: |
||||||
|
* <ul> |
||||||
|
* <li>Bean definitions will not change type.</li> |
||||||
|
* <li>Beans definitions will not be removed.</li> |
||||||
|
* <li>Beans will not be created in parallel.</li> |
||||||
|
* </ul> |
||||||
|
* |
||||||
|
* @author Phillip Webb |
||||||
|
* @since 1.2.0 |
||||||
|
*/ |
||||||
|
abstract class BeanTypeRegistry { |
||||||
|
|
||||||
|
public static long check; |
||||||
|
|
||||||
|
static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the names of beans matching the given type (including subclasses), judging |
||||||
|
* from either bean definitions or the value of {@code getObjectType} in the case of |
||||||
|
* FactoryBeans. Will include singletons but not cause early bean initialization. |
||||||
|
* @param type the class or interface to match (must not be {@code null}) |
||||||
|
* @return the names of beans (or objects created by FactoryBeans) matching the given |
||||||
|
* object type (including subclasses), or an empty set if none |
||||||
|
*/ |
||||||
|
public abstract Set<String> getNamesForType(Class<?> type); |
||||||
|
|
||||||
|
/** |
||||||
|
* Attempt to guess the type that a {@link FactoryBean} will return based on the |
||||||
|
* generics in its method signature. |
||||||
|
* @param beanFactory the source bean factory |
||||||
|
* @param definition the bean definition |
||||||
|
* @param name the name of the bean |
||||||
|
* @return the generic type of the {@link FactoryBean} or {@code null} |
||||||
|
*/ |
||||||
|
protected final Class<?> getFactoryBeanGeneric( |
||||||
|
ConfigurableListableBeanFactory beanFactory, BeanDefinition definition, |
||||||
|
String name) { |
||||||
|
try { |
||||||
|
return doGetFactoryBeanGeneric(beanFactory, definition, name); |
||||||
|
} |
||||||
|
catch (Exception ex) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Class<?> doGetFactoryBeanGeneric(ConfigurableListableBeanFactory beanFactory, |
||||||
|
BeanDefinition definition, String name) throws Exception, |
||||||
|
ClassNotFoundException, LinkageError { |
||||||
|
if (StringUtils.hasLength(definition.getFactoryBeanName()) |
||||||
|
&& StringUtils.hasLength(definition.getFactoryMethodName())) { |
||||||
|
return getConfigurationClassFactoryBeanGeneric(beanFactory, definition, name); |
||||||
|
} |
||||||
|
if (StringUtils.hasLength(definition.getBeanClassName())) { |
||||||
|
return getDirectFactoryBeanGeneric(beanFactory, definition, name); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private Class<?> getConfigurationClassFactoryBeanGeneric( |
||||||
|
ConfigurableListableBeanFactory beanFactory, BeanDefinition definition, |
||||||
|
String name) throws Exception { |
||||||
|
BeanDefinition factoryDefinition = beanFactory.getBeanDefinition(definition |
||||||
|
.getFactoryBeanName()); |
||||||
|
Class<?> factoryClass = ClassUtils.forName(factoryDefinition.getBeanClassName(), |
||||||
|
beanFactory.getBeanClassLoader()); |
||||||
|
Method method = ReflectionUtils.findMethod(factoryClass, |
||||||
|
definition.getFactoryMethodName()); |
||||||
|
Class<?> generic = ResolvableType.forMethodReturnType(method) |
||||||
|
.as(FactoryBean.class).resolveGeneric(); |
||||||
|
if ((generic == null || generic.equals(Object.class)) |
||||||
|
&& definition.hasAttribute(FACTORY_BEAN_OBJECT_TYPE)) { |
||||||
|
generic = (Class<?>) definition.getAttribute(FACTORY_BEAN_OBJECT_TYPE); |
||||||
|
} |
||||||
|
return generic; |
||||||
|
} |
||||||
|
|
||||||
|
private Class<?> getDirectFactoryBeanGeneric( |
||||||
|
ConfigurableListableBeanFactory beanFactory, BeanDefinition definition, |
||||||
|
String name) throws ClassNotFoundException, LinkageError { |
||||||
|
Class<?> factoryBeanClass = ClassUtils.forName(definition.getBeanClassName(), |
||||||
|
beanFactory.getBeanClassLoader()); |
||||||
|
Class<?> generic = ResolvableType.forClass(factoryBeanClass) |
||||||
|
.as(FactoryBean.class).resolveGeneric(); |
||||||
|
if ((generic == null || generic.equals(Object.class)) |
||||||
|
&& definition.hasAttribute(FACTORY_BEAN_OBJECT_TYPE)) { |
||||||
|
generic = (Class<?>) definition.getAttribute(FACTORY_BEAN_OBJECT_TYPE); |
||||||
|
} |
||||||
|
return generic; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Factory method to get the {@link BeanTypeRegistry} for a given {@link BeanFactory}. |
||||||
|
* @param beanFactory the source bean factory |
||||||
|
* @return the {@link BeanTypeRegistry} for the given bean factory |
||||||
|
*/ |
||||||
|
public static BeanTypeRegistry get(ListableBeanFactory beanFactory) { |
||||||
|
if (beanFactory instanceof DefaultListableBeanFactory) { |
||||||
|
DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory; |
||||||
|
if (listableBeanFactory.isAllowEagerClassLoading()) { |
||||||
|
return OptimizedBeanTypeRegistry.getFromFactory(listableBeanFactory); |
||||||
|
} |
||||||
|
} |
||||||
|
return new DefaultBeanTypeRegistry(beanFactory); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Default (non-optimized) {@link BeanTypeRegistry} implementation. |
||||||
|
*/ |
||||||
|
static class DefaultBeanTypeRegistry extends BeanTypeRegistry { |
||||||
|
|
||||||
|
private final ListableBeanFactory beanFactory; |
||||||
|
|
||||||
|
public DefaultBeanTypeRegistry(ListableBeanFactory beanFactory) { |
||||||
|
this.beanFactory = beanFactory; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Set<String> getNamesForType(Class<?> type) { |
||||||
|
Set<String> result = new LinkedHashSet<String>(); |
||||||
|
result.addAll(Arrays.asList(this.beanFactory.getBeanNamesForType(type, true, |
||||||
|
false))); |
||||||
|
if (this.beanFactory instanceof ConfigurableListableBeanFactory) { |
||||||
|
collectBeanNamesForTypeFromFactoryBeans(result, |
||||||
|
(ConfigurableListableBeanFactory) this.beanFactory, type); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
private void collectBeanNamesForTypeFromFactoryBeans(Set<String> result, |
||||||
|
ConfigurableListableBeanFactory beanFactory, Class<?> type) { |
||||||
|
String[] names = beanFactory.getBeanNamesForType(FactoryBean.class, true, |
||||||
|
false); |
||||||
|
for (String name : names) { |
||||||
|
name = BeanFactoryUtils.transformedBeanName(name); |
||||||
|
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name); |
||||||
|
Class<?> generic = getFactoryBeanGeneric(beanFactory, beanDefinition, |
||||||
|
name); |
||||||
|
if (generic != null && ClassUtils.isAssignable(type, generic)) { |
||||||
|
result.add(name); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link BeanTypeRegistry} optimized for {@link DefaultListableBeanFactory} |
||||||
|
* implementations that allow eager class loading. |
||||||
|
*/ |
||||||
|
static class OptimizedBeanTypeRegistry extends BeanTypeRegistry implements |
||||||
|
ApplicationListener<ContextRefreshedEvent> { |
||||||
|
|
||||||
|
private static final String BEAN_NAME = BeanTypeRegistry.class.getName(); |
||||||
|
|
||||||
|
private final DefaultListableBeanFactory beanFactory; |
||||||
|
|
||||||
|
private final Map<String, Class<?>> beanTypes = new HashMap<String, Class<?>>(); |
||||||
|
|
||||||
|
private int lastBeanDefinitionCount = 0; |
||||||
|
|
||||||
|
public OptimizedBeanTypeRegistry(DefaultListableBeanFactory beanFactory) { |
||||||
|
this.beanFactory = beanFactory; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void onApplicationEvent(ContextRefreshedEvent event) { |
||||||
|
// We're done at this point, free up some memory
|
||||||
|
this.beanTypes.clear(); |
||||||
|
this.lastBeanDefinitionCount = 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Set<String> getNamesForType(Class<?> type) { |
||||||
|
if (this.lastBeanDefinitionCount != this.beanFactory.getBeanDefinitionCount()) { |
||||||
|
Iterator<String> names = this.beanFactory.getBeanNamesIterator(); |
||||||
|
while (names.hasNext()) { |
||||||
|
String name = names.next(); |
||||||
|
if (!this.beanTypes.containsKey(name)) { |
||||||
|
addBeanType(name); |
||||||
|
} |
||||||
|
} |
||||||
|
this.lastBeanDefinitionCount = this.beanFactory.getBeanDefinitionCount(); |
||||||
|
} |
||||||
|
Set<String> matches = new LinkedHashSet<String>(); |
||||||
|
for (Map.Entry<String, Class<?>> entry : this.beanTypes.entrySet()) { |
||||||
|
if (entry.getValue() != null && type.isAssignableFrom(entry.getValue())) { |
||||||
|
matches.add(entry.getKey()); |
||||||
|
} |
||||||
|
} |
||||||
|
return matches; |
||||||
|
} |
||||||
|
|
||||||
|
private void addBeanType(String name) { |
||||||
|
if (this.beanFactory.containsSingleton(name)) { |
||||||
|
this.beanTypes.put(name, this.beanFactory.getType(name)); |
||||||
|
} |
||||||
|
else if (!this.beanFactory.isAlias(name)) { |
||||||
|
String factoryName = BeanFactory.FACTORY_BEAN_PREFIX + name; |
||||||
|
RootBeanDefinition beanDefinition = (RootBeanDefinition) this.beanFactory |
||||||
|
.getMergedBeanDefinition(name); |
||||||
|
if (!beanDefinition.isAbstract() |
||||||
|
&& !requiresEagerInit(beanDefinition.getFactoryBeanName())) { |
||||||
|
if (this.beanFactory.isFactoryBean(factoryName)) { |
||||||
|
Class<?> factoryBeanGeneric = getFactoryBeanGeneric( |
||||||
|
this.beanFactory, beanDefinition, name); |
||||||
|
this.beanTypes.put(name, factoryBeanGeneric); |
||||||
|
this.beanTypes.put(factoryName, |
||||||
|
this.beanFactory.getType(factoryName)); |
||||||
|
} |
||||||
|
else { |
||||||
|
this.beanTypes.put(name, this.beanFactory.getType(name)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private boolean requiresEagerInit(String factoryBeanName) { |
||||||
|
return (factoryBeanName != null |
||||||
|
&& this.beanFactory.isFactoryBean(factoryBeanName) && !this.beanFactory |
||||||
|
.containsSingleton(factoryBeanName)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the {@link OptimizedBeanTypeRegistry} for the given bean factory. |
||||||
|
*/ |
||||||
|
public static OptimizedBeanTypeRegistry getFromFactory( |
||||||
|
DefaultListableBeanFactory factory) { |
||||||
|
if (!factory.containsLocalBean(BEAN_NAME)) { |
||||||
|
BeanDefinition bd = new RootBeanDefinition( |
||||||
|
OptimizedBeanTypeRegistry.class); |
||||||
|
bd.getConstructorArgumentValues().addIndexedArgumentValue(0, factory); |
||||||
|
factory.registerBeanDefinition(BEAN_NAME, bd); |
||||||
|
|
||||||
|
} |
||||||
|
return factory.getBean(BEAN_NAME, OptimizedBeanTypeRegistry.class); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue