From ce0282406f6fd6cdf4ad68ca80917d5ed45f7abb Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Thu, 4 Apr 2019 15:39:48 -0700 Subject: [PATCH] Skip lazy init for beans that explicitly set lazy to false This commit also adds tests to ensure that the child management context works when lazy initialization is enabled. Also, it adds a BeanFactoryPostProcessor to the child context so that the server is created and listening for requests but other beans in the child context are not created until requested. See gh-16184 --- .../ManagementContextAutoConfiguration.java | 17 ++++++ .../main/asciidoc/spring-boot-features.adoc | 4 ++ ...nitializationBeanFactoryPostProcessor.java | 57 +++++++++++++++++++ .../boot/SpringApplication.java | 21 ------- .../boot/SpringApplicationTests.java | 33 +++++++++++ ...gementPortWithLazyInitializationTests.java | 55 ++++++++++++++++++ 6 files changed, 166 insertions(+), 21 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java create mode 100644 spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortWithLazyInitializationTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java index c7fa4ad0537..3e28e606d07 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/server/ManagementContextAutoConfiguration.java @@ -16,7 +16,11 @@ package org.springframework.boot.actuate.autoconfigure.web.server; +import java.util.List; + import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextFactory; import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType; @@ -33,6 +37,7 @@ import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -135,6 +140,10 @@ public class ManagementContextAutoConfiguration { .createManagementContext(this.applicationContext, EnableChildManagementContextConfiguration.class, PropertyPlaceholderAutoConfiguration.class); + if (isLazyInitialization()) { + managementContext.addBeanFactoryPostProcessor( + new LazyInitializationBeanFactoryPostProcessor()); + } managementContext.setServerNamespace("management"); managementContext.setId(this.applicationContext.getId() + ":management"); setClassLoaderIfPossible(managementContext); @@ -144,6 +153,14 @@ public class ManagementContextAutoConfiguration { } } + protected boolean isLazyInitialization() { + AbstractApplicationContext context = (AbstractApplicationContext) this.applicationContext; + List postProcessors = context + .getBeanFactoryPostProcessors(); + return postProcessors.stream().anyMatch(( + postProcessor) -> postProcessor instanceof LazyInitializationBeanFactoryPostProcessor); + } + private void setClassLoaderIfPossible(ConfigurableApplicationContext child) { if (child instanceof DefaultResourceLoader) { ((DefaultResourceLoader) child) diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index fd58bc89b04..df50149a954 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -119,6 +119,10 @@ on `SpringApplicationBuilder` or the `setLazyInitialization` method on spring.main.lazy-initialization=true ---- +TIP: If you want to disable lazy initialization for certain beans while using lazy +initialization for the rest of the application, you can explicitly set their lazy attribute +to false using the `@Lazy(false)` annotation. + [[boot-features-banner]] 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 new file mode 100644 index 00000000000..5bea70c5c0d --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/LazyInitializationBeanFactoryPostProcessor.java @@ -0,0 +1,57 @@ +/* + * 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.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.core.Ordered; + +/** + * {@link BeanFactoryPostProcessor} to set the lazy attribute on bean definition. + * + * @author Andy Wilkinson + * @author Madhura Bhave + * @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); + if (beanDefinition instanceof AbstractBeanDefinition) { + Boolean lazyInit = ((AbstractBeanDefinition) beanDefinition) + .getLazyInit(); + if (lazyInit != null && !lazyInit) { + continue; + } + } + beanDefinition.setLazyInit(true); + } + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index cf5a0a34a98..61d0f1178e4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -34,10 +34,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; -import org.springframework.beans.BeansException; import org.springframework.beans.CachedIntrospectionResults; import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; @@ -61,7 +59,6 @@ import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.GenericTypeResolver; -import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.ConfigurableConversionService; @@ -1345,22 +1342,4 @@ public class SpringApplication { return new LinkedHashSet<>(list); } - private static final class LazyInitializationBeanFactoryPostProcessor - implements BeanFactoryPostProcessor, Ordered { - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) - throws BeansException { - for (String name : beanFactory.getBeanDefinitionNames()) { - beanFactory.getBeanDefinition(name).setLazyInit(true); - } - } - - @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 0bbc794bbfe..5f0089dbb3c 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 @@ -75,6 +75,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import org.springframework.context.event.ApplicationEventMulticaster; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.SimpleApplicationEventMulticaster; @@ -1186,6 +1187,14 @@ public class SpringApplicationTests { .getBean(AtomicInteger.class)).hasValue(0); } + @Test + public void lazyInitializationShouldNotApplyToBeansThatAreExplicitlyNotLazy() { + assertThat(new SpringApplication(NotLazyInitializationConfig.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") { @@ -1475,6 +1484,30 @@ public class SpringApplicationTests { } + @Configuration(proxyBeanMethods = false) + static class NotLazyInitializationConfig { + + @Bean + public AtomicInteger counter() { + return new AtomicInteger(0); + } + + @Bean + @Lazy(false) + public NotLazyBean NotLazyBean(AtomicInteger counter) { + return new NotLazyBean(counter); + } + + static class NotLazyBean { + + NotLazyBean(AtomicInteger counter) { + counter.getAndIncrement(); + } + + } + + } + static class ExitStatusException extends RuntimeException implements ExitCodeGenerator { diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortWithLazyInitializationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortWithLazyInitializationTests.java new file mode 100644 index 00000000000..34076c1d1c8 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/ManagementPortWithLazyInitializationTests.java @@ -0,0 +1,55 @@ +/* + * 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 sample.actuator; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.actuate.autoconfigure.web.server.LocalManagementPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for separate management and main service ports when + * lazy-initialization is enabled. + * + * @author Madhura Bhave + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { + "management.server.port=0", "spring.main.lazy-initialization=true" }) +public class ManagementPortWithLazyInitializationTests { + + @LocalManagementPort + private int managementPort; + + @Test + public void testHealth() { + ResponseEntity entity = new TestRestTemplate() + .withBasicAuth("user", "password").getForEntity( + "http://localhost:" + this.managementPort + "/actuator/health", + String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(entity.getBody()).contains("\"status\":\"UP\""); + } + +}