Avoid eager initialization when finding beans by annotation

Closes gh-8269
pull/8458/head
Andy Wilkinson 8 years ago
parent 8a326a8713
commit 59d3a79c82

@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.condition; package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -40,6 +41,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardMethodMetadata; import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -85,7 +87,7 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
* @param beanFactory the source bean factory * @param beanFactory the source bean factory
* @return the {@link BeanTypeRegistry} for the given bean factory * @return the {@link BeanTypeRegistry} for the given bean factory
*/ */
public static BeanTypeRegistry create(ListableBeanFactory beanFactory) { static BeanTypeRegistry get(ListableBeanFactory beanFactory) {
Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory); Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory);
DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory; DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory;
Assert.isTrue(listableBeanFactory.isAllowEagerClassLoading(), Assert.isTrue(listableBeanFactory.isAllowEagerClassLoading(),
@ -101,26 +103,39 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
/** /**
* Return the names of beans matching the given type (including subclasses), judging * 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 * from either bean definitions or the value of {@link FactoryBean#getObjectType()} in
* FactoryBeans. Will include singletons but not cause early bean initialization. * the case of {@link FactoryBean FactoryBeans}. Will include singletons but will not
* cause early bean initialization.
* @param type the class or interface to match (must not be {@code null}) * @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 * @return the names of beans (or objects created by FactoryBeans) matching the given
* object type (including subclasses), or an empty set if none * object type (including subclasses), or an empty set if none
*/ */
Set<String> getNamesForType(Class<?> type) { Set<String> getNamesForType(Class<?> type) {
if (this.lastBeanDefinitionCount != this.beanFactory.getBeanDefinitionCount()) { updateTypesIfNecessary();
Iterator<String> names = this.beanFactory.getBeanNamesIterator(); Set<String> matches = new LinkedHashSet<String>();
while (names.hasNext()) { for (Map.Entry<String, Class<?>> entry : this.beanTypes.entrySet()) {
String name = names.next(); if (entry.getValue() != null && type.isAssignableFrom(entry.getValue())) {
if (!this.beanTypes.containsKey(name)) { matches.add(entry.getKey());
addBeanType(name);
} }
} }
this.lastBeanDefinitionCount = this.beanFactory.getBeanDefinitionCount(); return matches;
} }
/**
* Returns the names of beans annotated with the given {@code annotation}, judging
* from either bean definitions or the value of {@link FactoryBean#getObjectType()} in
* the case of {@link FactoryBean FactoryBeans}. Will include singletons but will not
* cause early bean initialization.
* @param annotation the annotation to match (must not be {@code null})
* @return the names of beans (or objects created by FactoryBeans) annoated with the
* given annotation, or an empty set if none
*/
Set<String> getNamesForAnnotation(Class<? extends Annotation> annotation) {
updateTypesIfNecessary();
Set<String> matches = new LinkedHashSet<String>(); Set<String> matches = new LinkedHashSet<String>();
for (Map.Entry<String, Class<?>> entry : this.beanTypes.entrySet()) { for (Map.Entry<String, Class<?>> entry : this.beanTypes.entrySet()) {
if (entry.getValue() != null && type.isAssignableFrom(entry.getValue())) { if (entry.getValue() != null && AnnotationUtils
.findAnnotation(entry.getValue(), annotation) != null) {
matches.add(entry.getKey()); matches.add(entry.getKey());
} }
} }
@ -183,6 +198,19 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
&& !this.beanFactory.containsSingleton(factoryBeanName)); && !this.beanFactory.containsSingleton(factoryBeanName));
} }
private void updateTypesIfNecessary() {
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();
}
}
/** /**
* Attempt to guess the type that a {@link FactoryBean} will return based on the * Attempt to guess the type that a {@link FactoryBean} will return based on the
* generics in its method signature. * generics in its method signature.

@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -59,8 +60,6 @@ import org.springframework.util.StringUtils;
@Order(Ordered.LOWEST_PRECEDENCE) @Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition { 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).
@ -184,7 +183,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) {
result.addAll(BeanTypeRegistry.create(beanFactory).getNamesForType(type)); result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type));
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) { if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory) BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
.getParentBeanFactory(); .getParentBeanFactory();
@ -198,34 +197,32 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
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 {
String[] result = NO_BEANS; Set<String> names = new HashSet<String>();
try { try {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Class<? extends Annotation> typeClass = (Class<? extends Annotation>) ClassUtils Class<? extends Annotation> annotationType = (Class<? extends Annotation>) ClassUtils
.forName(type, classLoader); .forName(type, classLoader);
result = beanFactory.getBeanNamesForAnnotation(typeClass); collectBeanNamesForAnnotation(names, beanFactory, annotationType,
if (considerHierarchy) { considerHierarchy);
if (beanFactory
.getParentBeanFactory() instanceof ConfigurableListableBeanFactory) {
String[] parentResult = getBeanNamesForAnnotation(
(ConfigurableListableBeanFactory) beanFactory
.getParentBeanFactory(),
type, classLoader, true);
List<String> resultList = new ArrayList<String>();
resultList.addAll(Arrays.asList(result));
for (String beanName : parentResult) {
if (!resultList.contains(beanName)
&& !beanFactory.containsLocalBean(beanName)) {
resultList.add(beanName);
}
} }
result = StringUtils.toStringArray(resultList); catch (ClassNotFoundException e) {
// Continue
} }
return StringUtils.toStringArray(names);
} }
return result;
private void collectBeanNamesForAnnotation(Set<String> names,
ListableBeanFactory beanFactory, Class<? extends Annotation> annotationType,
boolean considerHierarchy) {
names.addAll(
BeanTypeRegistry.get(beanFactory).getNamesForAnnotation(annotationType));
if (considerHierarchy) {
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
.getParentBeanFactory();
if (parent instanceof ListableBeanFactory) {
collectBeanNamesForAnnotation(names, (ListableBeanFactory) parent,
annotationType, considerHierarchy);
} }
catch (ClassNotFoundException ex) {
return NO_BEANS;
} }
} }

@ -16,10 +16,16 @@
package org.springframework.boot.autoconfigure.condition; package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Date; import java.util.Date;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.test.util.EnvironmentTestUtils;
@ -124,6 +130,15 @@ public class ConditionalOnBeanTests {
this.context.refresh(); this.context.refresh();
} }
@Test
public void beanProducedByFactoryBeanIsConsideredWhenMatchingOnAnnotation() {
this.context.register(FactoryBeanConfiguration.class,
OnAnnotationWithFactoryBeanConfiguration.class);
this.context.refresh();
assertThat(this.context.containsBean("bar")).isTrue();
assertThat(this.context.getBeansOfType(ExampleBean.class)).hasSize(1);
}
@Configuration @Configuration
@ConditionalOnBean(name = "foo") @ConditionalOnBean(name = "foo")
protected static class OnBeanNameConfiguration { protected static class OnBeanNameConfiguration {
@ -220,6 +235,27 @@ public class ConditionalOnBeanTests {
} }
@Configuration
static class FactoryBeanConfiguration {
@Bean
public ExampleFactoryBean exampleBeanFactoryBean() {
return new ExampleFactoryBean();
}
}
@Configuration
@ConditionalOnBean(annotation = TestAnnotation.class)
static class OnAnnotationWithFactoryBeanConfiguration {
@Bean
public String bar() {
return "bar";
}
}
protected static class WithPropertyPlaceholderClassNameRegistrar protected static class WithPropertyPlaceholderClassNameRegistrar
implements ImportBeanDefinitionRegistrar { implements ImportBeanDefinitionRegistrar {
@ -233,4 +269,46 @@ public class ConditionalOnBeanTests {
} }
public static class ExampleFactoryBean implements FactoryBean<ExampleBean> {
@Override
public ExampleBean getObject() throws Exception {
return new ExampleBean("fromFactory");
}
@Override
public Class<?> getObjectType() {
return ExampleBean.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
@TestAnnotation
public static class ExampleBean {
private String value;
public ExampleBean(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
}
} }

@ -16,6 +16,11 @@
package org.springframework.boot.autoconfigure.condition; package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Date; import java.util.Date;
import org.junit.Test; import org.junit.Test;
@ -285,6 +290,15 @@ public class ConditionalOnMissingBeanTests {
assertThat(child.getBeansOfType(ExampleBean.class)).hasSize(2); assertThat(child.getBeansOfType(ExampleBean.class)).hasSize(2);
} }
@Test
public void beanProducedByFactoryBeanIsConsideredWhenMatchingOnAnnotation() {
this.context.register(ConcreteFactoryBeanConfiguration.class,
OnAnnotationWithFactoryBeanConfiguration.class);
this.context.refresh();
assertThat(this.context.containsBean("bar")).isFalse();
assertThat(this.context.getBeansOfType(ExampleBean.class)).hasSize(1);
}
@Configuration @Configuration
protected static class OnBeanInParentsConfiguration { protected static class OnBeanInParentsConfiguration {
@ -500,6 +514,17 @@ public class ConditionalOnMissingBeanTests {
} }
@Configuration
@ConditionalOnMissingBean(annotation = TestAnnotation.class)
protected static class OnAnnotationWithFactoryBeanConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Configuration @Configuration
@EnableScheduling @EnableScheduling
protected static class FooConfiguration { protected static class FooConfiguration {
@ -554,6 +579,7 @@ public class ConditionalOnMissingBeanTests {
} }
@TestAnnotation
public static class ExampleBean { public static class ExampleBean {
private String value; private String value;
@ -623,4 +649,11 @@ public class ConditionalOnMissingBeanTests {
} }
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
}
} }

Loading…
Cancel
Save