Polish 'Support programmatic lazy-int exclusion'
See gh-16615
This commit is contained in:
parent
0f26f4d6e2
commit
3ffc5f2a30
|
@ -1,53 +0,0 @@
|
||||||
/*
|
|
||||||
* 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,11 +16,10 @@
|
||||||
|
|
||||||
package org.springframework.boot;
|
package org.springframework.boot;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
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;
|
||||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
@ -28,71 +27,61 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link BeanFactoryPostProcessor} to set the lazy attribute on bean definition.
|
* {@link BeanFactoryPostProcessor} to set lazy-init on bean definitions that not
|
||||||
*
|
* {@link LazyInitializationExcludeFilter excluded} and have not already had a value
|
||||||
* <P>
|
* explicitly set.
|
||||||
* 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
|
* @author Tyler Van Gorder
|
||||||
|
* @author Phillip Webb
|
||||||
* @since 2.2.0
|
* @since 2.2.0
|
||||||
|
* @see LazyInitializationExcludeFilter
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
|
// Take care not to force the eager init of factory beans when getting filters
|
||||||
List<EagerLoadingBeanDefinitionPredicate> eagerPredicateList = getEagerLoadingPredicatesFromContext(
|
Collection<LazyInitializationExcludeFilter> filters = beanFactory
|
||||||
beanFactory);
|
.getBeansOfType(LazyInitializationExcludeFilter.class, false, false).values();
|
||||||
|
|
||||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||||
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
|
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();
|
postProcess(beanFactory, filters, beanName, (AbstractBeanDefinition) beanDefinition);
|
||||||
if (lazyInit != null && !lazyInit) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postProcess(ConfigurableListableBeanFactory beanFactory,
|
||||||
|
Collection<LazyInitializationExcludeFilter> filters, String beanName,
|
||||||
|
AbstractBeanDefinition beanDefinition) {
|
||||||
|
Boolean lazyInit = beanDefinition.getLazyInit();
|
||||||
|
Class<?> beanType = getBeanType(beanFactory, beanName);
|
||||||
|
if (lazyInit == null && !isExcluded(filters, beanName, beanDefinition, beanType)) {
|
||||||
beanDefinition.setLazyInit(true);
|
beanDefinition.setLazyInit(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private Class<?> getBeanType(ConfigurableListableBeanFactory beanFactory, String beanName) {
|
||||||
* This method extracts the list of
|
try {
|
||||||
* {@link org.springframework.boot.EagerLoadingBeanDefinitionPredicate} beans from the
|
return beanFactory.getType(beanName, false);
|
||||||
* 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.
|
catch (NoSuchBeanDefinitionException ex) {
|
||||||
* @param beanFactory bean factory passed into the post-processor.
|
return null;
|
||||||
* @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());
|
|
||||||
|
|
||||||
|
private boolean isExcluded(Collection<LazyInitializationExcludeFilter> filters, String beanName,
|
||||||
|
AbstractBeanDefinition beanDefinition, Class<?> beanType) {
|
||||||
|
if (beanType != null) {
|
||||||
|
for (LazyInitializationExcludeFilter filter : filters) {
|
||||||
|
if (filter.isExcluded(beanName, beanDefinition, beanType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* 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 org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter that can be used to exclude beans definitions from having their
|
||||||
|
* {@link AbstractBeanDefinition#setLazyInit(boolean) lazy-int} set by the
|
||||||
|
* {@link LazyInitializationBeanFactoryPostProcessor}.
|
||||||
|
* <P>
|
||||||
|
* Primarily intended to allow 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). Adding an instance of this filter to the application context can be used for
|
||||||
|
* these edge cases.
|
||||||
|
* <P>
|
||||||
|
* A typical example would be something like this:
|
||||||
|
* <P>
|
||||||
|
* <pre><code>
|
||||||
|
* @Bean
|
||||||
|
* public static LazyInitializationExcludeFilter integrationLazyInitializationExcludeFilter() {
|
||||||
|
* return LazyInitializationExcludeFilter.forBeanTypes(IntegrationFlow.class);
|
||||||
|
* }</code></pre>
|
||||||
|
* <p>
|
||||||
|
* NOTE: Beans of this type will be instantiated very early in the spring application
|
||||||
|
* lifecycle so should generally be declared static and not have any dependencies.
|
||||||
|
*
|
||||||
|
* @author Tyler Van Gorder
|
||||||
|
* @author Philip Webb
|
||||||
|
* @since 2.2.0
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface LazyInitializationExcludeFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if the specified bean definition should be excluded from
|
||||||
|
* having {@code lazy-int} automatically set.
|
||||||
|
* @param beanName the bean name
|
||||||
|
* @param beanDefinition the bean definition
|
||||||
|
* @param beanType the bean type
|
||||||
|
* @return {@code true} if {@code lazy-int} should not be automatically set
|
||||||
|
*/
|
||||||
|
boolean isExcluded(String beanName, BeanDefinition beanDefinition, Class<?> beanType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method that creates a filter for the given bean types.
|
||||||
|
* @param types the filtered types
|
||||||
|
* @return a new filter instance
|
||||||
|
*/
|
||||||
|
static LazyInitializationExcludeFilter forBeanTypes(Class<?>... types) {
|
||||||
|
return (beanName, beanDefinition, beanType) -> {
|
||||||
|
for (Class<?> type : types) {
|
||||||
|
if (type.isAssignableFrom(beanType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2019 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 org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link LazyInitializationExcludeFilter}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
*/
|
||||||
|
class LazyInitializationExcludeFilterTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void forBeanTypesMatchesTypes() {
|
||||||
|
LazyInitializationExcludeFilter filter = LazyInitializationExcludeFilter.forBeanTypes(CharSequence.class,
|
||||||
|
Number.class);
|
||||||
|
String beanName = "test";
|
||||||
|
BeanDefinition beanDefinition = mock(BeanDefinition.class);
|
||||||
|
assertThat(filter.isExcluded(beanName, beanDefinition, CharSequence.class)).isTrue();
|
||||||
|
assertThat(filter.isExcluded(beanName, beanDefinition, String.class)).isTrue();
|
||||||
|
assertThat(filter.isExcluded(beanName, beanDefinition, StringBuilder.class)).isTrue();
|
||||||
|
assertThat(filter.isExcluded(beanName, beanDefinition, Number.class)).isTrue();
|
||||||
|
assertThat(filter.isExcluded(beanName, beanDefinition, Long.class)).isTrue();
|
||||||
|
assertThat(filter.isExcluded(beanName, beanDefinition, Boolean.class)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1107,15 +1107,15 @@ class SpringApplicationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void lazyInitializationShouldNotApplyToBeansThatAreExplicitlyNotLazy() {
|
void lazyInitializationIgnoresBeansThatAreExplicitlyNotLazy() {
|
||||||
assertThat(new SpringApplication(NotLazyInitializationConfig.class)
|
assertThat(new SpringApplication(NotLazyInitializationConfig.class)
|
||||||
.run("--spring.main.web-application-type=none", "--spring.main.lazy-initialization=true")
|
.run("--spring.main.web-application-type=none", "--spring.main.lazy-initialization=true")
|
||||||
.getBean(AtomicInteger.class)).hasValue(1);
|
.getBean(AtomicInteger.class)).hasValue(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void lazyInitializationShouldNotApplyToBeansThatMatchPredicate() {
|
void lazyInitializationIgnoresLazyInitializationExcludeFilteredBeans() {
|
||||||
assertThat(new SpringApplication(NotLazyInitializationPredicateConfig.class)
|
assertThat(new SpringApplication(LazyInitializationExcludeFilterConfig.class)
|
||||||
.run("--spring.main.web-application-type=none", "--spring.main.lazy-initialization=true")
|
.run("--spring.main.web-application-type=none", "--spring.main.lazy-initialization=true")
|
||||||
.getBean(AtomicInteger.class)).hasValue(1);
|
.getBean(AtomicInteger.class)).hasValue(1);
|
||||||
}
|
}
|
||||||
|
@ -1430,7 +1430,7 @@ class SpringApplicationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
static class NotLazyInitializationPredicateConfig {
|
static class LazyInitializationExcludeFilterConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
AtomicInteger counter() {
|
AtomicInteger counter() {
|
||||||
|
@ -1443,8 +1443,10 @@ class SpringApplicationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
static EagerLoadingBeanDefinitionPredicate eagerLoadingBeanDefinitionPredicate() {
|
static LazyInitializationExcludeFilter lazyInitializationExcludeFilter() {
|
||||||
return NotLazyBean.class::isAssignableFrom;
|
return LazyInitializationExcludeFilter.forBeanTypes(NotLazyBean.class);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class NotLazyBean {
|
static class NotLazyBean {
|
||||||
|
@ -1455,8 +1457,6 @@ class SpringApplicationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ExitStatusException extends RuntimeException implements ExitCodeGenerator {
|
static class ExitStatusException extends RuntimeException implements ExitCodeGenerator {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in New Issue