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
pull/18371/head
Tyler Van Gorder 5 years ago 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
*
* &#64;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; 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.BeansException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 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. * {@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 Andy Wilkinson
* @author Madhura Bhave * @author Madhura Bhave
* @author Tyler Van Gorder
* @since 2.2.0 * @since 2.2.0
*/ */
public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { public final class LazyInitializationBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered {
@Override @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 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) { if (beanDefinition instanceof AbstractBeanDefinition) {
Boolean lazyInit = ((AbstractBeanDefinition) beanDefinition).getLazyInit(); Boolean lazyInit = ((AbstractBeanDefinition) beanDefinition).getLazyInit();
if (lazyInit != null && !lazyInit) { 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 @Override
public int getOrder() { public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; return Ordered.HIGHEST_PRECEDENCE;

@ -1113,8 +1113,16 @@ class SpringApplicationTests {
.getBean(AtomicInteger.class)).hasValue(1); .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, private Condition<ConfigurableEnvironment> matchingPropertySource(final Class<?> propertySourceClass,
final String name) { final String name) {
return new Condition<ConfigurableEnvironment>("has property source") { return new Condition<ConfigurableEnvironment>("has property source") {
@Override @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 { static class ExitStatusException extends RuntimeException implements ExitCodeGenerator {
@Override @Override

Loading…
Cancel
Save