Skip to content

Commit 3ffc5f2

Browse files
committed
Polish 'Support programmatic lazy-int exclusion'
See gh-16615
1 parent 0f26f4d commit 3ffc5f2

File tree

5 files changed

+171
-112
lines changed

5 files changed

+171
-112
lines changed

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

-53
This file was deleted.

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

+38-49
Original file line numberDiff line numberDiff line change
@@ -16,83 +16,72 @@
1616

1717
package org.springframework.boot;
1818

19-
import java.util.ArrayList;
20-
import java.util.List;
21-
import java.util.Map;
19+
import java.util.Collection;
2220

2321
import org.springframework.beans.BeansException;
22+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2423
import org.springframework.beans.factory.config.BeanDefinition;
2524
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
2625
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2726
import org.springframework.beans.factory.support.AbstractBeanDefinition;
2827
import org.springframework.core.Ordered;
2928

3029
/**
31-
* {@link BeanFactoryPostProcessor} to set the lazy attribute on bean definition.
32-
*
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.
30+
* {@link BeanFactoryPostProcessor} to set lazy-init on bean definitions that not
31+
* {@link LazyInitializationExcludeFilter excluded} and have not already had a value
32+
* explicitly set.
4933
*
5034
* @author Andy Wilkinson
5135
* @author Madhura Bhave
5236
* @author Tyler Van Gorder
37+
* @author Phillip Webb
5338
* @since 2.2.0
39+
* @see LazyInitializationExcludeFilter
5440
*/
5541
public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
5642

5743
@Override
5844
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
59-
60-
List<EagerLoadingBeanDefinitionPredicate> eagerPredicateList = getEagerLoadingPredicatesFromContext(
61-
beanFactory);
62-
45+
// Take care not to force the eager init of factory beans when getting filters
46+
Collection<LazyInitializationExcludeFilter> filters = beanFactory
47+
.getBeansOfType(LazyInitializationExcludeFilter.class, false, false).values();
6348
for (String beanName : beanFactory.getBeanDefinitionNames()) {
6449
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
65-
if (eagerPredicateList.stream()
66-
.anyMatch((predicate) -> predicate.test(beanFactory.getType(beanName, false)))) {
67-
continue;
68-
}
6950
if (beanDefinition instanceof AbstractBeanDefinition) {
70-
Boolean lazyInit = ((AbstractBeanDefinition) beanDefinition).getLazyInit();
71-
if (lazyInit != null && !lazyInit) {
72-
continue;
73-
}
51+
postProcess(beanFactory, filters, beanName, (AbstractBeanDefinition) beanDefinition);
7452
}
75-
beanDefinition.setLazyInit(true);
7653
}
7754
}
7855

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);
56+
private void postProcess(ConfigurableListableBeanFactory beanFactory,
57+
Collection<LazyInitializationExcludeFilter> filters, String beanName,
58+
AbstractBeanDefinition beanDefinition) {
59+
Boolean lazyInit = beanDefinition.getLazyInit();
60+
Class<?> beanType = getBeanType(beanFactory, beanName);
61+
if (lazyInit == null && !isExcluded(filters, beanName, beanDefinition, beanType)) {
62+
beanDefinition.setLazyInit(true);
63+
}
64+
}
9365

94-
return new ArrayList<>(eagerPredicates.values());
66+
private Class<?> getBeanType(ConfigurableListableBeanFactory beanFactory, String beanName) {
67+
try {
68+
return beanFactory.getType(beanName, false);
69+
}
70+
catch (NoSuchBeanDefinitionException ex) {
71+
return null;
72+
}
73+
}
9574

75+
private boolean isExcluded(Collection<LazyInitializationExcludeFilter> filters, String beanName,
76+
AbstractBeanDefinition beanDefinition, Class<?> beanType) {
77+
if (beanType != null) {
78+
for (LazyInitializationExcludeFilter filter : filters) {
79+
if (filter.isExcluded(beanName, beanDefinition, beanType)) {
80+
return true;
81+
}
82+
}
83+
}
84+
return false;
9685
}
9786

9887
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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 org.springframework.beans.factory.config.BeanDefinition;
20+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
21+
22+
/**
23+
* Filter that can be used to exclude beans definitions from having their
24+
* {@link AbstractBeanDefinition#setLazyInit(boolean) lazy-int} set by the
25+
* {@link LazyInitializationBeanFactoryPostProcessor}.
26+
* <P>
27+
* Primarily intended to allow downstream projects to deal with edge-cases in which it is
28+
* not easy to support lazy-loading (such as in DSLs that dynamically create additional
29+
* beans). Adding an instance of this filter to the application context can be used for
30+
* these edge cases.
31+
* <P>
32+
* A typical example would be something like this:
33+
* <P>
34+
* <pre><code>
35+
* &#64;Bean
36+
* public static LazyInitializationExcludeFilter integrationLazyInitializationExcludeFilter() {
37+
* return LazyInitializationExcludeFilter.forBeanTypes(IntegrationFlow.class);
38+
* }</code></pre>
39+
* <p>
40+
* NOTE: Beans of this type will be instantiated very early in the spring application
41+
* lifecycle so should generally be declared static and not have any dependencies.
42+
*
43+
* @author Tyler Van Gorder
44+
* @author Philip Webb
45+
* @since 2.2.0
46+
*/
47+
@FunctionalInterface
48+
public interface LazyInitializationExcludeFilter {
49+
50+
/**
51+
* Returns {@code true} if the specified bean definition should be excluded from
52+
* having {@code lazy-int} automatically set.
53+
* @param beanName the bean name
54+
* @param beanDefinition the bean definition
55+
* @param beanType the bean type
56+
* @return {@code true} if {@code lazy-int} should not be automatically set
57+
*/
58+
boolean isExcluded(String beanName, BeanDefinition beanDefinition, Class<?> beanType);
59+
60+
/**
61+
* Factory method that creates a filter for the given bean types.
62+
* @param types the filtered types
63+
* @return a new filter instance
64+
*/
65+
static LazyInitializationExcludeFilter forBeanTypes(Class<?>... types) {
66+
return (beanName, beanDefinition, beanType) -> {
67+
for (Class<?> type : types) {
68+
if (type.isAssignableFrom(beanType)) {
69+
return true;
70+
}
71+
}
72+
return false;
73+
};
74+
}
75+
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2012-2019 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 org.junit.jupiter.api.Test;
20+
21+
import org.springframework.beans.factory.config.BeanDefinition;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
import static org.mockito.Mockito.mock;
25+
26+
/**
27+
* Tests for {@link LazyInitializationExcludeFilter}.
28+
*
29+
* @author Phillip Webb
30+
*/
31+
class LazyInitializationExcludeFilterTests {
32+
33+
@Test
34+
void forBeanTypesMatchesTypes() {
35+
LazyInitializationExcludeFilter filter = LazyInitializationExcludeFilter.forBeanTypes(CharSequence.class,
36+
Number.class);
37+
String beanName = "test";
38+
BeanDefinition beanDefinition = mock(BeanDefinition.class);
39+
assertThat(filter.isExcluded(beanName, beanDefinition, CharSequence.class)).isTrue();
40+
assertThat(filter.isExcluded(beanName, beanDefinition, String.class)).isTrue();
41+
assertThat(filter.isExcluded(beanName, beanDefinition, StringBuilder.class)).isTrue();
42+
assertThat(filter.isExcluded(beanName, beanDefinition, Number.class)).isTrue();
43+
assertThat(filter.isExcluded(beanName, beanDefinition, Long.class)).isTrue();
44+
assertThat(filter.isExcluded(beanName, beanDefinition, Boolean.class)).isFalse();
45+
}
46+
47+
}

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

+10-10
Original file line numberDiff line numberDiff line change
@@ -1107,15 +1107,15 @@ void lazyInitializationCanBeEnabled() {
11071107
}
11081108

11091109
@Test
1110-
void lazyInitializationShouldNotApplyToBeansThatAreExplicitlyNotLazy() {
1110+
void lazyInitializationIgnoresBeansThatAreExplicitlyNotLazy() {
11111111
assertThat(new SpringApplication(NotLazyInitializationConfig.class)
11121112
.run("--spring.main.web-application-type=none", "--spring.main.lazy-initialization=true")
11131113
.getBean(AtomicInteger.class)).hasValue(1);
11141114
}
11151115

11161116
@Test
1117-
void lazyInitializationShouldNotApplyToBeansThatMatchPredicate() {
1118-
assertThat(new SpringApplication(NotLazyInitializationPredicateConfig.class)
1117+
void lazyInitializationIgnoresLazyInitializationExcludeFilteredBeans() {
1118+
assertThat(new SpringApplication(LazyInitializationExcludeFilterConfig.class)
11191119
.run("--spring.main.web-application-type=none", "--spring.main.lazy-initialization=true")
11201120
.getBean(AtomicInteger.class)).hasValue(1);
11211121
}
@@ -1430,7 +1430,7 @@ static class NotLazyBean {
14301430
}
14311431

14321432
@Configuration(proxyBeanMethods = false)
1433-
static class NotLazyInitializationPredicateConfig {
1433+
static class LazyInitializationExcludeFilterConfig {
14341434

14351435
@Bean
14361436
AtomicInteger counter() {
@@ -1443,16 +1443,16 @@ NotLazyBean notLazyBean(AtomicInteger counter) {
14431443
}
14441444

14451445
@Bean
1446-
static EagerLoadingBeanDefinitionPredicate eagerLoadingBeanDefinitionPredicate() {
1447-
return NotLazyBean.class::isAssignableFrom;
1446+
static LazyInitializationExcludeFilter lazyInitializationExcludeFilter() {
1447+
return LazyInitializationExcludeFilter.forBeanTypes(NotLazyBean.class);
14481448
}
14491449

1450-
static class NotLazyBean {
1450+
}
14511451

1452-
NotLazyBean(AtomicInteger counter) {
1453-
counter.getAndIncrement();
1454-
}
1452+
static class NotLazyBean {
14551453

1454+
NotLazyBean(AtomicInteger counter) {
1455+
counter.getAndIncrement();
14561456
}
14571457

14581458
}

0 commit comments

Comments
 (0)