Commit 45b579c4 authored by Phillip Webb's avatar Phillip Webb

Improve OnBeanCondition Performance

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-1803
parent d6f2f0de
...@@ -27,18 +27,14 @@ import java.util.List; ...@@ -27,18 +27,14 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.HierarchicalBeanFactory; import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory; 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.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.ConfigurationCondition; import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.MethodMetadata;
...@@ -57,15 +53,16 @@ import org.springframework.util.StringUtils; ...@@ -57,15 +53,16 @@ import org.springframework.util.StringUtils;
* @author Jakub Kubrynski * @author Jakub Kubrynski
*/ */
@Order(Ordered.LOWEST_PRECEDENCE) @Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition { public class OnBeanCondition extends SpringBootCondition implements
ConfigurationCondition {
private static final String[] NO_BEANS = {};
/** /**
* Bean definition attribute name for factory beans to signal their product type (if * Bean definition attribute name for factory beans to signal their product type (if
* known and it can't be deduced from the factory bean class). * known and it can't be deduced from the factory bean class).
*/ */
public static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType"; public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE;
private static final String[] NO_BEANS = {};
@Override @Override
public ConfigurationPhase getConfigurationPhase() { public ConfigurationPhase getConfigurationPhase() {
...@@ -75,9 +72,7 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit ...@@ -75,9 +72,7 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
@Override @Override
public ConditionOutcome getMatchOutcome(ConditionContext context, public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) { AnnotatedTypeMetadata metadata) {
StringBuffer matchMessage = new StringBuffer(); StringBuffer matchMessage = new StringBuffer();
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) { if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata, BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnBean.class); ConditionalOnBean.class);
...@@ -89,7 +84,6 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit ...@@ -89,7 +84,6 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
matchMessage.append("@ConditionalOnBean " + spec + " found the following " matchMessage.append("@ConditionalOnBean " + spec + " found the following "
+ matching); + matching);
} }
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata, BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
ConditionalOnMissingBean.class); ConditionalOnMissingBean.class);
...@@ -101,12 +95,10 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit ...@@ -101,12 +95,10 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
matchMessage.append(matchMessage.length() == 0 ? "" : " "); matchMessage.append(matchMessage.length() == 0 ? "" : " ");
matchMessage.append("@ConditionalOnMissingBean " + spec + " found no beans"); matchMessage.append("@ConditionalOnMissingBean " + spec + " found no beans");
} }
return ConditionOutcome.match(matchMessage.toString()); return ConditionOutcome.match(matchMessage.toString());
} }
private List<String> getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { private List<String> getMatchingBeans(ConditionContext context, BeanSearchSpec beans) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beans.getStrategy() == SearchStrategy.PARENTS) { if (beans.getStrategy() == SearchStrategy.PARENTS) {
BeanFactory parent = beanFactory.getParentBeanFactory(); BeanFactory parent = beanFactory.getParentBeanFactory();
...@@ -145,9 +137,9 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit ...@@ -145,9 +137,9 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
return beanFactory.containsLocalBean(beanName); return beanFactory.containsLocalBean(beanName);
} }
private Collection<String> getBeanNamesForType( private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory,
ConfigurableListableBeanFactory beanFactory, String type, String type, ClassLoader classLoader, boolean considerHierarchy)
ClassLoader classLoader, boolean considerHierarchy) throws LinkageError { throws LinkageError {
try { try {
Set<String> result = new LinkedHashSet<String>(); Set<String> result = new LinkedHashSet<String>();
collectBeanNamesForType(result, beanFactory, collectBeanNamesForType(result, beanFactory,
...@@ -161,12 +153,7 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit ...@@ -161,12 +153,7 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
private void collectBeanNamesForType(Set<String> result, private void collectBeanNamesForType(Set<String> result,
ListableBeanFactory beanFactory, Class<?> type, boolean considerHierarchy) { ListableBeanFactory beanFactory, Class<?> type, boolean considerHierarchy) {
// eagerInit set to false to prevent early instantiation result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type));
result.addAll(Arrays.asList(beanFactory.getBeanNamesForType(type, true, false)));
if (beanFactory instanceof ConfigurableListableBeanFactory) {
collectBeanNamesForTypeFromFactoryBeans(result,
(ConfigurableListableBeanFactory) beanFactory, type);
}
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) { if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory) BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
.getParentBeanFactory(); .getParentBeanFactory();
...@@ -177,73 +164,6 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit ...@@ -177,73 +164,6 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
} }
} }
/**
* Attempt to collect bean names for type by considering FactoryBean generics. Some
* factory beans will not be able to determine their object type at this stage, so
* those are not eligible for matching this condition.
*/
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);
}
}
}
private Class<?> getFactoryBeanGeneric(ConfigurableListableBeanFactory beanFactory,
BeanDefinition definition, String name) {
try {
if (StringUtils.hasLength(definition.getFactoryBeanName())
&& StringUtils.hasLength(definition.getFactoryMethodName())) {
return getConfigurationClassFactoryBeanGeneric(beanFactory, definition,
name);
}
if (StringUtils.hasLength(definition.getBeanClassName())) {
return getDirectFactoryBeanGeneric(beanFactory, definition, name);
}
}
catch (Exception ex) {
}
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;
}
private String[] getBeanNamesForAnnotation( private String[] getBeanNamesForAnnotation(
ConfigurableListableBeanFactory beanFactory, String type, ConfigurableListableBeanFactory beanFactory, String type,
ClassLoader classLoader, boolean considerHierarchy) throws LinkageError { ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment