Skip to content

Commit 8db598b

Browse files
committed
Merge pull request #16615 from tkvangorder
* pr/16615: Polish 'Support programmatic lazy-int exclusion' Support programmatic lazy-int exclusion Closes gh-16615
2 parents 78996b1 + 3ffc5f2 commit 8db598b

File tree

4 files changed

+205
-8
lines changed

4 files changed

+205
-8
lines changed

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

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,74 @@
1616

1717
package org.springframework.boot;
1818

19+
import java.util.Collection;
20+
1921
import org.springframework.beans.BeansException;
22+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2023
import org.springframework.beans.factory.config.BeanDefinition;
2124
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
2225
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2326
import org.springframework.beans.factory.support.AbstractBeanDefinition;
2427
import org.springframework.core.Ordered;
2528

2629
/**
27-
* {@link BeanFactoryPostProcessor} to set the lazy attribute on bean definition.
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.
2833
*
2934
* @author Andy Wilkinson
3035
* @author Madhura Bhave
36+
* @author Tyler Van Gorder
37+
* @author Phillip Webb
3138
* @since 2.2.0
39+
* @see LazyInitializationExcludeFilter
3240
*/
3341
public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
3442

3543
@Override
3644
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
37-
for (String name : beanFactory.getBeanDefinitionNames()) {
38-
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
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();
48+
for (String beanName : beanFactory.getBeanDefinitionNames()) {
49+
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
3950
if (beanDefinition instanceof AbstractBeanDefinition) {
40-
Boolean lazyInit = ((AbstractBeanDefinition) beanDefinition).getLazyInit();
41-
if (lazyInit != null && !lazyInit) {
42-
continue;
43-
}
51+
postProcess(beanFactory, filters, beanName, (AbstractBeanDefinition) beanDefinition);
4452
}
53+
}
54+
}
55+
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)) {
4562
beanDefinition.setLazyInit(true);
4663
}
4764
}
4865

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+
}
74+
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;
85+
}
86+
4987
@Override
5088
public int getOrder() {
5189
return Ordered.HIGHEST_PRECEDENCE;
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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1107,14 +1107,22 @@ 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

1116+
@Test
1117+
void lazyInitializationIgnoresLazyInitializationExcludeFilteredBeans() {
1118+
assertThat(new SpringApplication(LazyInitializationExcludeFilterConfig.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 LazyInitializationExcludeFilterConfig {
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 LazyInitializationExcludeFilter lazyInitializationExcludeFilter() {
1447+
return LazyInitializationExcludeFilter.forBeanTypes(NotLazyBean.class);
1448+
}
1449+
1450+
}
1451+
1452+
static class NotLazyBean {
1453+
1454+
NotLazyBean(AtomicInteger counter) {
1455+
counter.getAndIncrement();
1456+
}
1457+
1458+
}
1459+
14241460
static class ExitStatusException extends RuntimeException implements ExitCodeGenerator {
14251461

14261462
@Override

0 commit comments

Comments
 (0)