Skip to content

Commit 0f26f4d

Browse files
Tyler Van Gorderphilwebb
Tyler Van Gorder
authored andcommitted
Support programmatic lazy-int exclusion
Allow the `LazyInitializationBeanFactoryPostProcessor` to skip setting lazy-init based on a programmatic callback. This feature allows downstream projects to deal with edge-cases in which it is not easy to support lazy-loading (such as in DSLs that dynamically create additional beans). See gh-16615
1 parent 78996b1 commit 0f26f4d

File tree

3 files changed

+140
-2
lines changed

3 files changed

+140
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot;
18+
19+
import java.util.function.Predicate;
20+
21+
/**
22+
* This predicate can be implemented by downstream projects to customize the behavior of
23+
* the {@link LazyInitializationBeanFactoryPostProcessor}.
24+
*
25+
* <P>
26+
* There are edge cases (such as in DSLs that dynamically create additional beans) in
27+
* which it is not easy to explicitly exclude a class from the lazy-loading behavior.
28+
* Adding an instance of this predicate to the application context can be used for these
29+
* edge cases.
30+
* <P>
31+
* Returning "true" from this predicate will exclude a class from the lazy-loading
32+
* process.
33+
*
34+
* <P>
35+
* Example:
36+
* <P>
37+
* <pre>
38+
* {@code
39+
*
40+
* &#64;Bean
41+
* public static EagerLoadingBeanDefinitionPredicate eagerLoadingBeanDefinitionPredicate() {
42+
* return IntegrationFlow.class::isAssignableFrom;
43+
* }}</pre>
44+
*
45+
* WARNING: Beans of this type will be instantiated very early in the spring application
46+
* life cycle.
47+
*
48+
* @author Tyler Van Gorder
49+
* @since 2.2.0
50+
*/
51+
public interface EagerLoadingBeanDefinitionPredicate extends Predicate<Class<?>> {
52+
53+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.boot;
1818

19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.Map;
22+
1923
import org.springframework.beans.BeansException;
2024
import org.springframework.beans.factory.config.BeanDefinition;
2125
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
@@ -26,16 +30,42 @@
2630
/**
2731
* {@link BeanFactoryPostProcessor} to set the lazy attribute on bean definition.
2832
*
33+
* <P>
34+
* This processor will not touch a bean definition that has already had its "lazy" flag
35+
* explicitly set to "false".
36+
*
37+
* <P>
38+
* There are edge cases in which it is not easy to explicitly set the "lazy" flag to
39+
* "false" (such as in DSLs that dynamically create additional beans) and therefore this
40+
* class uses a customizer strategy that allows downstream projects to contribute
41+
* predicates which impact if a class is considered for lazy-loading.
42+
*
43+
* <P>
44+
* Because this is a BeanFactoryPostProcessor, this class does not use dependency
45+
* injection to collect the customizers. The post processor actually makes two passes
46+
* through the bean definitions; the first is used to find and instantiate any
47+
* {@link org.springframework.boot.EagerLoadingBeanDefinitionPredicate} and the second
48+
* pass is where bean definitions are marked as lazy.
49+
*
2950
* @author Andy Wilkinson
3051
* @author Madhura Bhave
52+
* @author Tyler Van Gorder
3153
* @since 2.2.0
3254
*/
3355
public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
3456

3557
@Override
3658
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
37-
for (String name : beanFactory.getBeanDefinitionNames()) {
38-
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
59+
60+
List<EagerLoadingBeanDefinitionPredicate> eagerPredicateList = getEagerLoadingPredicatesFromContext(
61+
beanFactory);
62+
63+
for (String beanName : beanFactory.getBeanDefinitionNames()) {
64+
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
65+
if (eagerPredicateList.stream()
66+
.anyMatch((predicate) -> predicate.test(beanFactory.getType(beanName, false)))) {
67+
continue;
68+
}
3969
if (beanDefinition instanceof AbstractBeanDefinition) {
4070
Boolean lazyInit = ((AbstractBeanDefinition) beanDefinition).getLazyInit();
4171
if (lazyInit != null && !lazyInit) {
@@ -46,6 +76,25 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
4676
}
4777
}
4878

79+
/**
80+
* This method extracts the list of
81+
* {@link org.springframework.boot.EagerLoadingBeanDefinitionPredicate} beans from the
82+
* bean factory. Because this method is called early in the factory life cycle, we
83+
* take care not to force the eager initialization of factory beans.
84+
* @param beanFactory bean factory passed into the post-processor.
85+
* @return a list of {@link EagerLoadingBeanDefinitionPredicate} that can be used to
86+
* customize the behavior of this processor.
87+
*/
88+
private List<EagerLoadingBeanDefinitionPredicate> getEagerLoadingPredicatesFromContext(
89+
ConfigurableListableBeanFactory beanFactory) {
90+
91+
Map<String, EagerLoadingBeanDefinitionPredicate> eagerPredicates = beanFactory
92+
.getBeansOfType(EagerLoadingBeanDefinitionPredicate.class, false, false);
93+
94+
return new ArrayList<>(eagerPredicates.values());
95+
96+
}
97+
4998
@Override
5099
public int getOrder() {
51100
return Ordered.HIGHEST_PRECEDENCE;

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,8 +1113,16 @@ void lazyInitializationShouldNotApplyToBeansThatAreExplicitlyNotLazy() {
11131113
.getBean(AtomicInteger.class)).hasValue(1);
11141114
}
11151115

1116+
@Test
1117+
void lazyInitializationShouldNotApplyToBeansThatMatchPredicate() {
1118+
assertThat(new SpringApplication(NotLazyInitializationPredicateConfig.class)
1119+
.run("--spring.main.web-application-type=none", "--spring.main.lazy-initialization=true")
1120+
.getBean(AtomicInteger.class)).hasValue(1);
1121+
}
1122+
11161123
private Condition<ConfigurableEnvironment> matchingPropertySource(final Class<?> propertySourceClass,
11171124
final String name) {
1125+
11181126
return new Condition<ConfigurableEnvironment>("has property source") {
11191127

11201128
@Override
@@ -1421,6 +1429,34 @@ static class NotLazyBean {
14211429

14221430
}
14231431

1432+
@Configuration(proxyBeanMethods = false)
1433+
static class NotLazyInitializationPredicateConfig {
1434+
1435+
@Bean
1436+
AtomicInteger counter() {
1437+
return new AtomicInteger(0);
1438+
}
1439+
1440+
@Bean
1441+
NotLazyBean notLazyBean(AtomicInteger counter) {
1442+
return new NotLazyBean(counter);
1443+
}
1444+
1445+
@Bean
1446+
static EagerLoadingBeanDefinitionPredicate eagerLoadingBeanDefinitionPredicate() {
1447+
return NotLazyBean.class::isAssignableFrom;
1448+
}
1449+
1450+
static class NotLazyBean {
1451+
1452+
NotLazyBean(AtomicInteger counter) {
1453+
counter.getAndIncrement();
1454+
}
1455+
1456+
}
1457+
1458+
}
1459+
14241460
static class ExitStatusException extends RuntimeException implements ExitCodeGenerator {
14251461

14261462
@Override

0 commit comments

Comments
 (0)