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
This commit is contained in:
committed by
Phillip Webb
parent
78996b126b
commit
0f26f4d6e2
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2012-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* This predicate can be implemented by downstream projects to customize the behavior of
|
||||
* the {@link LazyInitializationBeanFactoryPostProcessor}.
|
||||
*
|
||||
* <P>
|
||||
* There are edge cases (such as in DSLs that dynamically create additional beans) in
|
||||
* which it is not easy to explicitly exclude a class from the lazy-loading behavior.
|
||||
* Adding an instance of this predicate to the application context can be used for these
|
||||
* edge cases.
|
||||
* <P>
|
||||
* Returning "true" from this predicate will exclude a class from the lazy-loading
|
||||
* process.
|
||||
*
|
||||
* <P>
|
||||
* Example:
|
||||
* <P>
|
||||
* <pre>
|
||||
* {@code
|
||||
*
|
||||
* @Bean
|
||||
* public static EagerLoadingBeanDefinitionPredicate eagerLoadingBeanDefinitionPredicate() {
|
||||
* return IntegrationFlow.class::isAssignableFrom;
|
||||
* }}</pre>
|
||||
*
|
||||
* WARNING: Beans of this type will be instantiated very early in the spring application
|
||||
* life cycle.
|
||||
*
|
||||
* @author Tyler Van Gorder
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public interface EagerLoadingBeanDefinitionPredicate extends Predicate<Class<?>> {
|
||||
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package org.springframework.boot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
@@ -26,16 +30,42 @@ import org.springframework.core.Ordered;
|
||||
/**
|
||||
* {@link BeanFactoryPostProcessor} to set the lazy attribute on bean definition.
|
||||
*
|
||||
* <P>
|
||||
* This processor will not touch a bean definition that has already had its "lazy" flag
|
||||
* explicitly set to "false".
|
||||
*
|
||||
* <P>
|
||||
* There are edge cases in which it is not easy to explicitly set the "lazy" flag to
|
||||
* "false" (such as in DSLs that dynamically create additional beans) and therefore this
|
||||
* class uses a customizer strategy that allows downstream projects to contribute
|
||||
* predicates which impact if a class is considered for lazy-loading.
|
||||
*
|
||||
* <P>
|
||||
* Because this is a BeanFactoryPostProcessor, this class does not use dependency
|
||||
* injection to collect the customizers. The post processor actually makes two passes
|
||||
* through the bean definitions; the first is used to find and instantiate any
|
||||
* {@link org.springframework.boot.EagerLoadingBeanDefinitionPredicate} and the second
|
||||
* pass is where bean definitions are marked as lazy.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Madhura Bhave
|
||||
* @author Tyler Van Gorder
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
for (String name : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
|
||||
|
||||
List<EagerLoadingBeanDefinitionPredicate> eagerPredicateList = getEagerLoadingPredicatesFromContext(
|
||||
beanFactory);
|
||||
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
|
||||
if (eagerPredicateList.stream()
|
||||
.anyMatch((predicate) -> predicate.test(beanFactory.getType(beanName, false)))) {
|
||||
continue;
|
||||
}
|
||||
if (beanDefinition instanceof AbstractBeanDefinition) {
|
||||
Boolean lazyInit = ((AbstractBeanDefinition) beanDefinition).getLazyInit();
|
||||
if (lazyInit != null && !lazyInit) {
|
||||
@@ -46,6 +76,25 @@ public final class LazyInitializationBeanFactoryPostProcessor implements BeanFac
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method extracts the list of
|
||||
* {@link org.springframework.boot.EagerLoadingBeanDefinitionPredicate} beans from the
|
||||
* bean factory. Because this method is called early in the factory life cycle, we
|
||||
* take care not to force the eager initialization of factory beans.
|
||||
* @param beanFactory bean factory passed into the post-processor.
|
||||
* @return a list of {@link EagerLoadingBeanDefinitionPredicate} that can be used to
|
||||
* customize the behavior of this processor.
|
||||
*/
|
||||
private List<EagerLoadingBeanDefinitionPredicate> getEagerLoadingPredicatesFromContext(
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
|
||||
Map<String, EagerLoadingBeanDefinitionPredicate> eagerPredicates = beanFactory
|
||||
.getBeansOfType(EagerLoadingBeanDefinitionPredicate.class, false, false);
|
||||
|
||||
return new ArrayList<>(eagerPredicates.values());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
|
||||
@@ -1113,8 +1113,16 @@ class SpringApplicationTests {
|
||||
.getBean(AtomicInteger.class)).hasValue(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lazyInitializationShouldNotApplyToBeansThatMatchPredicate() {
|
||||
assertThat(new SpringApplication(NotLazyInitializationPredicateConfig.class)
|
||||
.run("--spring.main.web-application-type=none", "--spring.main.lazy-initialization=true")
|
||||
.getBean(AtomicInteger.class)).hasValue(1);
|
||||
}
|
||||
|
||||
private Condition<ConfigurableEnvironment> matchingPropertySource(final Class<?> propertySourceClass,
|
||||
final String name) {
|
||||
|
||||
return new Condition<ConfigurableEnvironment>("has property source") {
|
||||
|
||||
@Override
|
||||
@@ -1421,6 +1429,34 @@ class SpringApplicationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class NotLazyInitializationPredicateConfig {
|
||||
|
||||
@Bean
|
||||
AtomicInteger counter() {
|
||||
return new AtomicInteger(0);
|
||||
}
|
||||
|
||||
@Bean
|
||||
NotLazyBean notLazyBean(AtomicInteger counter) {
|
||||
return new NotLazyBean(counter);
|
||||
}
|
||||
|
||||
@Bean
|
||||
static EagerLoadingBeanDefinitionPredicate eagerLoadingBeanDefinitionPredicate() {
|
||||
return NotLazyBean.class::isAssignableFrom;
|
||||
}
|
||||
|
||||
static class NotLazyBean {
|
||||
|
||||
NotLazyBean(AtomicInteger counter) {
|
||||
counter.getAndIncrement();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ExitStatusException extends RuntimeException implements ExitCodeGenerator {
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user