Skip to content

Support programmatic lazy-int exclusion #16615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<?>> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,16 +30,42 @@
/**
* {@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) {
Expand All @@ -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<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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConfigurableEnvironment> matchingPropertySource(final Class<?> propertySourceClass,
final String name) {

return new Condition<ConfigurableEnvironment>("has property source") {

@Override
Expand Down Expand Up @@ -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
Expand Down