Commit 3a4a431e authored by Andy Wilkinson's avatar Andy Wilkinson

Provide a configuration option to enable lazy initialization

Closes gh-15870
parent 6519e85b
......@@ -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.
......
......@@ -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
......
......@@ -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<Object> 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;
}
}
}
......@@ -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<String, Object> getMapFromKeyValuePairs(String[] properties) {
Map<String, Object> map = new HashMap<>();
for (String property : properties) {
......
/*
* 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<HttpHandler> delegate;
private LazyHttpHandler(Mono<HttpHandler> delegate) {
this.delegate = delegate;
}
@Override
public Mono<Void> 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<Void> 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<HttpHandler> 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();
}
}
......
......@@ -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",
......
......@@ -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<ConfigurableEnvironment> matchingPropertySource(
final Class<?> propertySourceClass, final String name) {
return new Condition<ConfigurableEnvironment>("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 {
......
/*
* 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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment