From 6e1103b403e24a03f9637b267dfa0853de6192a4 Mon Sep 17 00:00:00 2001 From: Tyler Van Gorder Date: Tue, 3 Sep 2019 18:12:04 -0700 Subject: [PATCH] Adding predicates to exclude beans from lazy loading --- .../EagerLoadingBeanDefinitionPredicate.java | 53 +++++++++++++++++++ ...nitializationBeanFactoryPostProcessor.java | 53 ++++++++++++++++++- .../boot/SpringApplicationTests.java | 36 +++++++++++++ 3 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/EagerLoadingBeanDefinitionPredicate.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/EagerLoadingBeanDefinitionPredicate.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/EagerLoadingBeanDefinitionPredicate.java new file mode 100644 index 000000000000..fd9c5706d874 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/EagerLoadingBeanDefinitionPredicate.java @@ -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}. + * + *

+ * 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. + *

+ * Returning "true" from this predicate will exclude a class from the lazy-loading + * process. + * + *

+ * Example: + *

+ *

+ * {@code
+ *
+ * @Bean
+ * public static EagerLoadingBeanDefinitionPredicate eagerLoadingBeanDefinitionPredicate() {
+ *   return IntegrationFlow.class::isAssignableFrom;
+ * }}
+ * + * 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> { + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java index e66135986d17..64aeb9d41919 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java @@ -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 @@ /** * {@link BeanFactoryPostProcessor} to set the lazy attribute on bean definition. * + *

+ * This processor will not touch a bean definition that has already had its "lazy" flag + * explicitly set to "false". + * + *

+ * 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. + * + *

+ * 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 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 void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) } } + /** + * 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 getEagerLoadingPredicatesFromContext( + ConfigurableListableBeanFactory beanFactory) { + + Map eagerPredicates = beanFactory + .getBeansOfType(EagerLoadingBeanDefinitionPredicate.class, false, false); + + return new ArrayList<>(eagerPredicates.values()); + + } + @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index 9575deb03d04..63d9b4087b7c 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -1113,8 +1113,16 @@ void lazyInitializationShouldNotApplyToBeansThatAreExplicitlyNotLazy() { .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 matchingPropertySource(final Class propertySourceClass, final String name) { + return new Condition("has property source") { @Override @@ -1421,6 +1429,34 @@ static class NotLazyBean { } + @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