diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix/application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix/application-properties.adoc index ade50020089..2cf97e29e82 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix/application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix/application-properties.adoc @@ -125,6 +125,7 @@ content into your application. Rather, pick only the properties that you need. # APPLICATION SETTINGS ({sc-spring-boot}/SpringApplication.{sc-ext}[SpringApplication]) spring.main.allow-bean-definition-overriding=false # Whether bean definition overriding, by registering a definition with the same name as an existing definition, is allowed. spring.main.banner-mode=console # Mode used to display the banner when the application runs. + spring.main.lazy-initialization=false # Whether initialization should be performed lazily. spring.main.sources= # Sources (class names, package names, or XML resource locations) to include in the ApplicationContext. spring.main.web-application-type= # Flag to explicitly request a specific type of web application. If not set, auto-detected based on the classpath. 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 6b52940e441..a32d99c5490 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 @@ -91,6 +91,36 @@ the `debug` property as follows: +[[boot-features-lazy-initialization]] +=== Lazy Initialization +`SpringApplication` allows an application to be initialized lazily. When lazy +initialization is enabled, beans are created as they are needed rather than during +application startup. As a result, enabling lazy initialization can reduce the time that +it takes your application to start. In a web application, enabling lazy initialization +will result in many web-related beans not being initialized until an HTTP request is +received. + +A downside of lazy initialization is that it can delay the discovery of a problem with +the application. If a misconfigured bean is initialized lazily, a failure will no longer +occur during startup and the problem will only become apparent when the bean is +initialized. Care must also be taken to ensure that the JVM has sufficient memory to +accommodate all of the application's beans and not just those that are initialized during +startup. For these reasons, lazy initialization is not enabled by default and it is +recommended that fine-tuning of the JVM's heap size is done before enabling lazy +initialization. + +Lazy initialization can be enabled programatically using the `lazyInitialization` method +on `SpringApplicationBuilder` or the `setLazyInitialization` method on +`SpringApplication`. Alternatively, it can be enabled using the +`spring.main.lazy-initialization` property as shown in the following example: + +[source,properties,indent=0] +---- + spring.main.lazy-initialization=true +---- + + + [[boot-features-banner]] === Customizing the Banner The banner that is printed on start up can be changed by adding a `banner.txt` file to 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 7f4f1d37227..0c968fa0f57 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,7 +34,10 @@ 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; @@ -58,6 +61,7 @@ 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; @@ -235,6 +239,8 @@ public class SpringApplication { private boolean isCustomEnvironment = false; + private boolean lazyInitialization = false; + /** * Create a new {@link SpringApplication} instance. The application context will load * beans from the specified primary sources (see {@link SpringApplication class-level} @@ -386,6 +392,10 @@ public class SpringApplication { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } + if (this.lazyInitialization) { + context.addBeanFactoryPostProcessor( + new LazyInitializationBeanFactoryPostProcessor()); + } // Load the sources Set sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); @@ -979,6 +989,16 @@ public class SpringApplication { this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; } + /** + * Sets if beans should be initialized lazily. Defaults to {@code false}. + * @param lazyInitialization if initialization should be lazy + * @since 2.2 + * @see BeanDefinition#setLazyInit(boolean) + */ + public void setLazyInitialization(boolean lazyInitialization) { + this.lazyInitialization = lazyInitialization; + } + /** * Sets if the application is headless and should not instantiate AWT. Defaults to * {@code true} to prevent java icons appearing. @@ -1325,4 +1345,22 @@ 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/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java index 50615b90b03..9d6d5ae11e7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java @@ -395,6 +395,17 @@ public class SpringApplicationBuilder { return properties(getMapFromKeyValuePairs(defaultProperties)); } + /** + * Flag to control whether the application should be initialized lazily. + * @param lazyInitialization the flag to set. Defaults to false. + * @return the current builder + * @since 2.2 + */ + public SpringApplicationBuilder lazyInitialization(boolean lazyInitialization) { + this.application.setLazyInitialization(lazyInitialization); + return this; + } + private Map getMapFromKeyValuePairs(String[] properties) { Map map = new HashMap<>(); for (String property : properties) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java index c8fb42d227b..61ea60673da 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/reactive/context/ReactiveWebServerApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * 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. @@ -87,18 +87,15 @@ public class ReactiveWebServerApplicationContext private void createWebServer() { ServerManager serverManager = this.serverManager; if (serverManager == null) { - this.serverManager = ServerManager.get(getWebServerFactory()); + String webServerFactoryBeanName = getWebServerFactoryBeanName(); + boolean lazyInit = getBeanFactory() + .getBeanDefinition(webServerFactoryBeanName).isLazyInit(); + this.serverManager = ServerManager.get(getWebServerFactory(), lazyInit); } initPropertySources(); } - /** - * Return the {@link ReactiveWebServerFactory} that should be used to create the - * reactive web server. By default this method searches for a suitable bean in the - * context itself. - * @return a {@link ReactiveWebServerFactory} (never {@code null}) - */ - protected ReactiveWebServerFactory getWebServerFactory() { + protected String getWebServerFactoryBeanName() { // Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory() .getBeanNamesForType(ReactiveWebServerFactory.class); @@ -113,7 +110,24 @@ public class ReactiveWebServerApplicationContext + "ReactiveWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } - return getBeanFactory().getBean(beanNames[0], ReactiveWebServerFactory.class); + return beanNames[0]; + } + + protected ReactiveWebServerFactory getWebServerFactory(String factoryBeanName) { + return getBeanFactory().getBean(factoryBeanName, ReactiveWebServerFactory.class); + } + + /** + * Return the {@link ReactiveWebServerFactory} that should be used to create the + * reactive web server. By default this method searches for a suitable bean in the + * context itself. + * @return a {@link ReactiveWebServerFactory} (never {@code null}) + * @deprecated since 2.2 in favor of {@link #getWebServerFactoryBeanName()} and + * {@link #getWebServerFactory(String)} + */ + @Deprecated + protected ReactiveWebServerFactory getWebServerFactory() { + return getWebServerFactory(getWebServerFactoryBeanName()); } @Override @@ -187,6 +201,24 @@ public class ReactiveWebServerApplicationContext this.serverNamespace = serverNamespace; } + /** + * {@link HttpHandler} that initializes its delegate on first request. + */ + private static final class LazyHttpHandler implements HttpHandler { + + private final Mono delegate; + + private LazyHttpHandler(Mono delegate) { + this.delegate = delegate; + } + + @Override + public Mono handle(ServerHttpRequest request, ServerHttpResponse response) { + return this.delegate.flatMap((handler) -> handler.handle(request, response)); + } + + } + /** * Internal class used to manage the server and the {@link HttpHandler}, taking care * not to initialize the handler too early. @@ -195,11 +227,14 @@ public class ReactiveWebServerApplicationContext private final WebServer server; + private final boolean lazyInit; + private volatile HttpHandler handler; - private ServerManager(ReactiveWebServerFactory factory) { + private ServerManager(ReactiveWebServerFactory factory, boolean lazyInit) { this.handler = this::handleUninitialized; this.server = factory.getWebServer(this); + this.lazyInit = lazyInit; } private Mono handleUninitialized(ServerHttpRequest request, @@ -217,8 +252,9 @@ public class ReactiveWebServerApplicationContext return this.handler; } - public static ServerManager get(ReactiveWebServerFactory factory) { - return new ServerManager(factory); + public static ServerManager get(ReactiveWebServerFactory factory, + boolean lazyInit) { + return new ServerManager(factory, lazyInit); } public static WebServer getWebServer(ServerManager manager) { @@ -228,7 +264,9 @@ public class ReactiveWebServerApplicationContext public static void start(ServerManager manager, Supplier handlerSupplier) { if (manager != null && manager.server != null) { - manager.handler = handlerSupplier.get(); + manager.handler = manager.lazyInit + ? new LazyHttpHandler(Mono.fromSupplier(handlerSupplier)) + : handlerSupplier.get(); manager.server.start(); } } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 0642fd831d1..68498b6ded5 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -218,6 +218,13 @@ "description": "Mode used to display the banner when the application runs.", "defaultValue": "console" }, + { + "name": "spring.main.lazy-initialization", + "type": "java.lang.Boolean", + "sourceType": "org.springframework.boot.SpringApplication", + "description": "Whether initialization should be performed lazily.", + "defaultValue": false + }, { "name": "spring.main.show-banner", "type": "java.lang.Boolean", 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 31ac59be55d..965c31d16f1 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 @@ -22,6 +22,7 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.PostConstruct; @@ -1171,6 +1172,21 @@ public class SpringApplicationTests { .getBean("someBean")).isEqualTo("override"); } + @Test + public void lazyInitializationIsDisabledByDefault() { + assertThat(new SpringApplication(LazyInitializationConfig.class) + .run("--spring.main.web-application-type=none") + .getBean(AtomicInteger.class)).hasValue(1); + } + + @Test + public void lazyInitializationCanBeEnabled() { + assertThat(new SpringApplication(LazyInitializationConfig.class) + .run("--spring.main.web-application-type=none", + "--spring.main.lazy-initialization=true") + .getBean(AtomicInteger.class)).hasValue(0); + } + private Condition matchingPropertySource( final Class propertySourceClass, final String name) { return new Condition("has property source") { @@ -1437,6 +1453,29 @@ public class SpringApplicationTests { } + @Configuration + static class LazyInitializationConfig { + + @Bean + public AtomicInteger counter() { + return new AtomicInteger(0); + } + + @Bean + public LazyBean lazyBean(AtomicInteger counter) { + return new LazyBean(counter); + } + + static class LazyBean { + + LazyBean(AtomicInteger counter) { + counter.incrementAndGet(); + } + + } + + } + static class ExitStatusException extends RuntimeException implements ExitCodeGenerator { diff --git a/spring-boot-samples/spring-boot-sample-webflux/src/main/java/sample/webflux/SampleWebFluxApplication.java b/spring-boot-samples/spring-boot-sample-webflux/src/main/java/sample/webflux/SampleWebFluxApplication.java index 05085b07587..62baa6b1b24 100644 --- a/spring-boot-samples/spring-boot-sample-webflux/src/main/java/sample/webflux/SampleWebFluxApplication.java +++ b/spring-boot-samples/spring-boot-sample-webflux/src/main/java/sample/webflux/SampleWebFluxApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2018 the original author or authors. + * 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. @@ -29,7 +29,7 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r public class SampleWebFluxApplication { public static void main(String[] args) { - SpringApplication.run(SampleWebFluxApplication.class); + SpringApplication.run(SampleWebFluxApplication.class, args); } @Bean