From 45b579c4397dee55b2f4bd4395605644ba5267ed Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sun, 2 Nov 2014 13:29:31 -0800 Subject: [PATCH] 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 --- .../condition/BeanTypeRegistry.java | 285 ++++++++++++++++++ .../condition/OnBeanCondition.java | 98 +----- 2 files changed, 294 insertions(+), 89 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/BeanTypeRegistry.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/BeanTypeRegistry.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/BeanTypeRegistry.java new file mode 100644 index 00000000000..5ba4027c971 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/BeanTypeRegistry.java @@ -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: + * + * + * @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 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 getNamesForType(Class type) { + Set result = new LinkedHashSet(); + 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 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 { + + private static final String BEAN_NAME = BeanTypeRegistry.class.getName(); + + private final DefaultListableBeanFactory beanFactory; + + private final Map> beanTypes = new HashMap>(); + + 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 getNamesForType(Class type) { + if (this.lastBeanDefinitionCount != this.beanFactory.getBeanDefinitionCount()) { + Iterator names = this.beanFactory.getBeanNamesIterator(); + while (names.hasNext()) { + String name = names.next(); + if (!this.beanTypes.containsKey(name)) { + addBeanType(name); + } + } + this.lastBeanDefinitionCount = this.beanFactory.getBeanDefinitionCount(); + } + Set matches = new LinkedHashSet(); + for (Map.Entry> 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); + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index ed70312a148..f7213429507 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -27,18 +27,14 @@ import java.util.List; 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.HierarchicalBeanFactory; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConfigurationCondition; import org.springframework.core.Ordered; -import org.springframework.core.ResolvableType; import org.springframework.core.annotation.Order; import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.MethodMetadata; @@ -57,15 +53,16 @@ import org.springframework.util.StringUtils; * @author Jakub Kubrynski */ @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 * known and it can't be deduced from the factory bean class). */ - public static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType"; - - private static final String[] NO_BEANS = {}; + public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE; @Override public ConfigurationPhase getConfigurationPhase() { @@ -75,9 +72,7 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit @Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - StringBuffer matchMessage = new StringBuffer(); - if (metadata.isAnnotated(ConditionalOnBean.class.getName())) { BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnBean.class); @@ -89,7 +84,6 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit matchMessage.append("@ConditionalOnBean " + spec + " found the following " + matching); } - if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { BeanSearchSpec spec = new BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class); @@ -101,12 +95,10 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit matchMessage.append(matchMessage.length() == 0 ? "" : " "); matchMessage.append("@ConditionalOnMissingBean " + spec + " found no beans"); } - return ConditionOutcome.match(matchMessage.toString()); } private List getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); if (beans.getStrategy() == SearchStrategy.PARENTS) { BeanFactory parent = beanFactory.getParentBeanFactory(); @@ -145,9 +137,9 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit return beanFactory.containsLocalBean(beanName); } - private Collection getBeanNamesForType( - ConfigurableListableBeanFactory beanFactory, String type, - ClassLoader classLoader, boolean considerHierarchy) throws LinkageError { + private Collection getBeanNamesForType(ListableBeanFactory beanFactory, + String type, ClassLoader classLoader, boolean considerHierarchy) + throws LinkageError { try { Set result = new LinkedHashSet(); collectBeanNamesForType(result, beanFactory, @@ -161,12 +153,7 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit private void collectBeanNamesForType(Set result, ListableBeanFactory beanFactory, Class type, boolean considerHierarchy) { - // eagerInit set to false to prevent early instantiation - result.addAll(Arrays.asList(beanFactory.getBeanNamesForType(type, true, false))); - if (beanFactory instanceof ConfigurableListableBeanFactory) { - collectBeanNamesForTypeFromFactoryBeans(result, - (ConfigurableListableBeanFactory) beanFactory, type); - } + result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type)); if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) { BeanFactory parent = ((HierarchicalBeanFactory) beanFactory) .getParentBeanFactory(); @@ -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 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( ConfigurableListableBeanFactory beanFactory, String type, ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {