Skip to content

Commit 59d3a79

Browse files
committed
Avoid eager initialization when finding beans by annotation
Closes gh-8269
1 parent 8a326a8 commit 59d3a79

File tree

4 files changed

+175
-39
lines changed

4 files changed

+175
-39
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/BeanTypeRegistry.java

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.autoconfigure.condition;
1818

19+
import java.lang.annotation.Annotation;
1920
import java.lang.reflect.Method;
2021
import java.util.Arrays;
2122
import java.util.HashMap;
@@ -40,6 +41,7 @@
4041
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
4142
import org.springframework.beans.factory.support.RootBeanDefinition;
4243
import org.springframework.core.ResolvableType;
44+
import org.springframework.core.annotation.AnnotationUtils;
4345
import org.springframework.core.type.MethodMetadata;
4446
import org.springframework.core.type.StandardMethodMetadata;
4547
import org.springframework.util.Assert;
@@ -85,7 +87,7 @@ private BeanTypeRegistry(DefaultListableBeanFactory beanFactory) {
8587
* @param beanFactory the source bean factory
8688
* @return the {@link BeanTypeRegistry} for the given bean factory
8789
*/
88-
public static BeanTypeRegistry create(ListableBeanFactory beanFactory) {
90+
static BeanTypeRegistry get(ListableBeanFactory beanFactory) {
8991
Assert.isInstanceOf(DefaultListableBeanFactory.class, beanFactory);
9092
DefaultListableBeanFactory listableBeanFactory = (DefaultListableBeanFactory) beanFactory;
9193
Assert.isTrue(listableBeanFactory.isAllowEagerClassLoading(),
@@ -101,26 +103,39 @@ public static BeanTypeRegistry create(ListableBeanFactory beanFactory) {
101103

102104
/**
103105
* Return the names of beans matching the given type (including subclasses), judging
104-
* from either bean definitions or the value of {@code getObjectType} in the case of
105-
* FactoryBeans. Will include singletons but not cause early bean initialization.
106+
* from either bean definitions or the value of {@link FactoryBean#getObjectType()} in
107+
* the case of {@link FactoryBean FactoryBeans}. Will include singletons but will not
108+
* cause early bean initialization.
106109
* @param type the class or interface to match (must not be {@code null})
107110
* @return the names of beans (or objects created by FactoryBeans) matching the given
108111
* object type (including subclasses), or an empty set if none
109112
*/
110113
Set<String> getNamesForType(Class<?> type) {
111-
if (this.lastBeanDefinitionCount != this.beanFactory.getBeanDefinitionCount()) {
112-
Iterator<String> names = this.beanFactory.getBeanNamesIterator();
113-
while (names.hasNext()) {
114-
String name = names.next();
115-
if (!this.beanTypes.containsKey(name)) {
116-
addBeanType(name);
117-
}
114+
updateTypesIfNecessary();
115+
Set<String> matches = new LinkedHashSet<String>();
116+
for (Map.Entry<String, Class<?>> entry : this.beanTypes.entrySet()) {
117+
if (entry.getValue() != null && type.isAssignableFrom(entry.getValue())) {
118+
matches.add(entry.getKey());
118119
}
119-
this.lastBeanDefinitionCount = this.beanFactory.getBeanDefinitionCount();
120120
}
121+
return matches;
122+
}
123+
124+
/**
125+
* Returns the names of beans annotated with the given {@code annotation}, judging
126+
* from either bean definitions or the value of {@link FactoryBean#getObjectType()} in
127+
* the case of {@link FactoryBean FactoryBeans}. Will include singletons but will not
128+
* cause early bean initialization.
129+
* @param annotation the annotation to match (must not be {@code null})
130+
* @return the names of beans (or objects created by FactoryBeans) annoated with the
131+
* given annotation, or an empty set if none
132+
*/
133+
Set<String> getNamesForAnnotation(Class<? extends Annotation> annotation) {
134+
updateTypesIfNecessary();
121135
Set<String> matches = new LinkedHashSet<String>();
122136
for (Map.Entry<String, Class<?>> entry : this.beanTypes.entrySet()) {
123-
if (entry.getValue() != null && type.isAssignableFrom(entry.getValue())) {
137+
if (entry.getValue() != null && AnnotationUtils
138+
.findAnnotation(entry.getValue(), annotation) != null) {
124139
matches.add(entry.getKey());
125140
}
126141
}
@@ -183,6 +198,19 @@ private boolean requiresEagerInit(String factoryBeanName) {
183198
&& !this.beanFactory.containsSingleton(factoryBeanName));
184199
}
185200

201+
private void updateTypesIfNecessary() {
202+
if (this.lastBeanDefinitionCount != this.beanFactory.getBeanDefinitionCount()) {
203+
Iterator<String> names = this.beanFactory.getBeanNamesIterator();
204+
while (names.hasNext()) {
205+
String name = names.next();
206+
if (!this.beanTypes.containsKey(name)) {
207+
addBeanType(name);
208+
}
209+
}
210+
this.lastBeanDefinitionCount = this.beanFactory.getBeanDefinitionCount();
211+
}
212+
}
213+
186214
/**
187215
* Attempt to guess the type that a {@link FactoryBean} will return based on the
188216
* generics in its method signature.

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Arrays;
2323
import java.util.Collection;
2424
import java.util.Collections;
25+
import java.util.HashSet;
2526
import java.util.LinkedHashSet;
2627
import java.util.List;
2728
import java.util.Set;
@@ -59,8 +60,6 @@
5960
@Order(Ordered.LOWEST_PRECEDENCE)
6061
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {
6162

62-
private static final String[] NO_BEANS = {};
63-
6463
/**
6564
* Bean definition attribute name for factory beans to signal their product type (if
6665
* known and it can't be deduced from the factory bean class).
@@ -184,7 +183,7 @@ private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory,
184183

185184
private void collectBeanNamesForType(Set<String> result,
186185
ListableBeanFactory beanFactory, Class<?> type, boolean considerHierarchy) {
187-
result.addAll(BeanTypeRegistry.create(beanFactory).getNamesForType(type));
186+
result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type));
188187
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
189188
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
190189
.getParentBeanFactory();
@@ -198,34 +197,32 @@ private void collectBeanNamesForType(Set<String> result,
198197
private String[] getBeanNamesForAnnotation(
199198
ConfigurableListableBeanFactory beanFactory, String type,
200199
ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
201-
String[] result = NO_BEANS;
200+
Set<String> names = new HashSet<String>();
202201
try {
203202
@SuppressWarnings("unchecked")
204-
Class<? extends Annotation> typeClass = (Class<? extends Annotation>) ClassUtils
203+
Class<? extends Annotation> annotationType = (Class<? extends Annotation>) ClassUtils
205204
.forName(type, classLoader);
206-
result = beanFactory.getBeanNamesForAnnotation(typeClass);
207-
if (considerHierarchy) {
208-
if (beanFactory
209-
.getParentBeanFactory() instanceof ConfigurableListableBeanFactory) {
210-
String[] parentResult = getBeanNamesForAnnotation(
211-
(ConfigurableListableBeanFactory) beanFactory
212-
.getParentBeanFactory(),
213-
type, classLoader, true);
214-
List<String> resultList = new ArrayList<String>();
215-
resultList.addAll(Arrays.asList(result));
216-
for (String beanName : parentResult) {
217-
if (!resultList.contains(beanName)
218-
&& !beanFactory.containsLocalBean(beanName)) {
219-
resultList.add(beanName);
220-
}
221-
}
222-
result = StringUtils.toStringArray(resultList);
223-
}
224-
}
225-
return result;
205+
collectBeanNamesForAnnotation(names, beanFactory, annotationType,
206+
considerHierarchy);
226207
}
227-
catch (ClassNotFoundException ex) {
228-
return NO_BEANS;
208+
catch (ClassNotFoundException e) {
209+
// Continue
210+
}
211+
return StringUtils.toStringArray(names);
212+
}
213+
214+
private void collectBeanNamesForAnnotation(Set<String> names,
215+
ListableBeanFactory beanFactory, Class<? extends Annotation> annotationType,
216+
boolean considerHierarchy) {
217+
names.addAll(
218+
BeanTypeRegistry.get(beanFactory).getNamesForAnnotation(annotationType));
219+
if (considerHierarchy) {
220+
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
221+
.getParentBeanFactory();
222+
if (parent instanceof ListableBeanFactory) {
223+
collectBeanNamesForAnnotation(names, (ListableBeanFactory) parent,
224+
annotationType, considerHierarchy);
225+
}
229226
}
230227
}
231228

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616

1717
package org.springframework.boot.autoconfigure.condition;
1818

19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
1924
import java.util.Date;
2025

2126
import org.junit.Test;
2227

28+
import org.springframework.beans.factory.FactoryBean;
2329
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2430
import org.springframework.beans.factory.support.RootBeanDefinition;
2531
import org.springframework.boot.test.util.EnvironmentTestUtils;
@@ -124,6 +130,15 @@ public void withPropertyPlaceholderClassName() throws Exception {
124130
this.context.refresh();
125131
}
126132

133+
@Test
134+
public void beanProducedByFactoryBeanIsConsideredWhenMatchingOnAnnotation() {
135+
this.context.register(FactoryBeanConfiguration.class,
136+
OnAnnotationWithFactoryBeanConfiguration.class);
137+
this.context.refresh();
138+
assertThat(this.context.containsBean("bar")).isTrue();
139+
assertThat(this.context.getBeansOfType(ExampleBean.class)).hasSize(1);
140+
}
141+
127142
@Configuration
128143
@ConditionalOnBean(name = "foo")
129144
protected static class OnBeanNameConfiguration {
@@ -220,6 +235,27 @@ protected static class WithPropertyPlaceholderClassName {
220235

221236
}
222237

238+
@Configuration
239+
static class FactoryBeanConfiguration {
240+
241+
@Bean
242+
public ExampleFactoryBean exampleBeanFactoryBean() {
243+
return new ExampleFactoryBean();
244+
}
245+
246+
}
247+
248+
@Configuration
249+
@ConditionalOnBean(annotation = TestAnnotation.class)
250+
static class OnAnnotationWithFactoryBeanConfiguration {
251+
252+
@Bean
253+
public String bar() {
254+
return "bar";
255+
}
256+
257+
}
258+
223259
protected static class WithPropertyPlaceholderClassNameRegistrar
224260
implements ImportBeanDefinitionRegistrar {
225261

@@ -233,4 +269,46 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
233269

234270
}
235271

272+
public static class ExampleFactoryBean implements FactoryBean<ExampleBean> {
273+
274+
@Override
275+
public ExampleBean getObject() throws Exception {
276+
return new ExampleBean("fromFactory");
277+
}
278+
279+
@Override
280+
public Class<?> getObjectType() {
281+
return ExampleBean.class;
282+
}
283+
284+
@Override
285+
public boolean isSingleton() {
286+
return false;
287+
}
288+
289+
}
290+
291+
@TestAnnotation
292+
public static class ExampleBean {
293+
294+
private String value;
295+
296+
public ExampleBean(String value) {
297+
this.value = value;
298+
}
299+
300+
@Override
301+
public String toString() {
302+
return this.value;
303+
}
304+
305+
}
306+
307+
@Target(ElementType.TYPE)
308+
@Retention(RetentionPolicy.RUNTIME)
309+
@Documented
310+
public @interface TestAnnotation {
311+
312+
}
313+
236314
}

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616

1717
package org.springframework.boot.autoconfigure.condition;
1818

19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
1924
import java.util.Date;
2025

2126
import org.junit.Test;
@@ -285,6 +290,15 @@ public void currentContextIsIgnoredWhenUsingParentsStrategy() {
285290
assertThat(child.getBeansOfType(ExampleBean.class)).hasSize(2);
286291
}
287292

293+
@Test
294+
public void beanProducedByFactoryBeanIsConsideredWhenMatchingOnAnnotation() {
295+
this.context.register(ConcreteFactoryBeanConfiguration.class,
296+
OnAnnotationWithFactoryBeanConfiguration.class);
297+
this.context.refresh();
298+
assertThat(this.context.containsBean("bar")).isFalse();
299+
assertThat(this.context.getBeansOfType(ExampleBean.class)).hasSize(1);
300+
}
301+
288302
@Configuration
289303
protected static class OnBeanInParentsConfiguration {
290304

@@ -500,6 +514,17 @@ public String bar() {
500514

501515
}
502516

517+
@Configuration
518+
@ConditionalOnMissingBean(annotation = TestAnnotation.class)
519+
protected static class OnAnnotationWithFactoryBeanConfiguration {
520+
521+
@Bean
522+
public String bar() {
523+
return "bar";
524+
}
525+
526+
}
527+
503528
@Configuration
504529
@EnableScheduling
505530
protected static class FooConfiguration {
@@ -554,6 +579,7 @@ public ExampleBean exampleBean2() {
554579

555580
}
556581

582+
@TestAnnotation
557583
public static class ExampleBean {
558584

559585
private String value;
@@ -623,4 +649,11 @@ public boolean isSingleton() {
623649

624650
}
625651

652+
@Target(ElementType.TYPE)
653+
@Retention(RetentionPolicy.RUNTIME)
654+
@Documented
655+
public @interface TestAnnotation {
656+
657+
}
658+
626659
}

0 commit comments

Comments
 (0)