Avoid eager initialization when finding beans by annotation
Closes gh-8269
This commit is contained in:
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,23 +103,15 @@ 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();
|
|
||||||
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>();
|
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 && type.isAssignableFrom(entry.getValue())) {
|
||||||
|
@ -127,6 +121,27 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
|
||||||
return matches;
|
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>();
|
||||||
|
for (Map.Entry<String, Class<?>> entry : this.beanTypes.entrySet()) {
|
||||||
|
if (entry.getValue() != null && AnnotationUtils
|
||||||
|
.findAnnotation(entry.getValue(), annotation) != null) {
|
||||||
|
matches.add(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterSingletonsInstantiated() {
|
public void afterSingletonsInstantiated() {
|
||||||
// We're done at this point, free up some memory
|
// We're done at this point, free up some memory
|
||||||
|
@ -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,35 +197,33 @@ 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,
|
||||||
|
considerHierarchy);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e) {
|
||||||
|
// Continue
|
||||||
|
}
|
||||||
|
return StringUtils.toStringArray(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectBeanNamesForAnnotation(Set<String> names,
|
||||||
|
ListableBeanFactory beanFactory, Class<? extends Annotation> annotationType,
|
||||||
|
boolean considerHierarchy) {
|
||||||
|
names.addAll(
|
||||||
|
BeanTypeRegistry.get(beanFactory).getNamesForAnnotation(annotationType));
|
||||||
if (considerHierarchy) {
|
if (considerHierarchy) {
|
||||||
if (beanFactory
|
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
|
||||||
.getParentBeanFactory() instanceof ConfigurableListableBeanFactory) {
|
.getParentBeanFactory();
|
||||||
String[] parentResult = getBeanNamesForAnnotation(
|
if (parent instanceof ListableBeanFactory) {
|
||||||
(ConfigurableListableBeanFactory) beanFactory
|
collectBeanNamesForAnnotation(names, (ListableBeanFactory) parent,
|
||||||
.getParentBeanFactory(),
|
annotationType, considerHierarchy);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException ex) {
|
|
||||||
return NO_BEANS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasSingleAutowireCandidate(
|
private boolean hasSingleAutowireCandidate(
|
||||||
|
|
|
@ -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…
Reference in New Issue