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. ...@@ -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]) # 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.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.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.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. 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: ...@@ -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]] [[boot-features-banner]]
=== Customizing the Banner === Customizing the Banner
The banner that is printed on start up can be changed by adding a `banner.txt` file to 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; ...@@ -34,7 +34,10 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.CachedIntrospectionResults; 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.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader; import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
...@@ -58,6 +61,7 @@ import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; ...@@ -58,6 +61,7 @@ import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.GenericTypeResolver; import org.springframework.core.GenericTypeResolver;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.ConfigurableConversionService;
...@@ -235,6 +239,8 @@ public class SpringApplication { ...@@ -235,6 +239,8 @@ public class SpringApplication {
private boolean isCustomEnvironment = false; private boolean isCustomEnvironment = false;
private boolean lazyInitialization = false;
/** /**
* Create a new {@link SpringApplication} instance. The application context will load * Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level} * beans from the specified primary sources (see {@link SpringApplication class-level}
...@@ -386,6 +392,10 @@ public class SpringApplication { ...@@ -386,6 +392,10 @@ public class SpringApplication {
((DefaultListableBeanFactory) beanFactory) ((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
} }
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(
new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources // Load the sources
Set<Object> sources = getAllSources(); Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty"); Assert.notEmpty(sources, "Sources must not be empty");
...@@ -979,6 +989,16 @@ public class SpringApplication { ...@@ -979,6 +989,16 @@ public class SpringApplication {
this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; 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 * Sets if the application is headless and should not instantiate AWT. Defaults to
* {@code true} to prevent java icons appearing. * {@code true} to prevent java icons appearing.
...@@ -1325,4 +1345,22 @@ public class SpringApplication { ...@@ -1325,4 +1345,22 @@ public class SpringApplication {
return new LinkedHashSet<>(list); 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 { ...@@ -395,6 +395,17 @@ public class SpringApplicationBuilder {
return properties(getMapFromKeyValuePairs(defaultProperties)); 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) { private Map<String, Object> getMapFromKeyValuePairs(String[] properties) {
Map<String, Object> map = new HashMap<>(); Map<String, Object> map = new HashMap<>();
for (String property : properties) { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -87,18 +87,15 @@ public class ReactiveWebServerApplicationContext ...@@ -87,18 +87,15 @@ public class ReactiveWebServerApplicationContext
private void createWebServer() { private void createWebServer() {
ServerManager serverManager = this.serverManager; ServerManager serverManager = this.serverManager;
if (serverManager == null) { if (serverManager == null) {
this.serverManager = ServerManager.get(getWebServerFactory()); String webServerFactoryBeanName = getWebServerFactoryBeanName();
boolean lazyInit = getBeanFactory()
.getBeanDefinition(webServerFactoryBeanName).isLazyInit();
this.serverManager = ServerManager.get(getWebServerFactory(), lazyInit);
} }
initPropertySources(); initPropertySources();
} }
/** protected String getWebServerFactoryBeanName() {
* 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() {
// Use bean names so that we don't consider the hierarchy // Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory() String[] beanNames = getBeanFactory()
.getBeanNamesForType(ReactiveWebServerFactory.class); .getBeanNamesForType(ReactiveWebServerFactory.class);
...@@ -113,7 +110,24 @@ public class ReactiveWebServerApplicationContext ...@@ -113,7 +110,24 @@ public class ReactiveWebServerApplicationContext
+ "ReactiveWebServerFactory beans : " + "ReactiveWebServerFactory beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames)); + 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 @Override
...@@ -187,6 +201,24 @@ public class ReactiveWebServerApplicationContext ...@@ -187,6 +201,24 @@ public class ReactiveWebServerApplicationContext
this.serverNamespace = serverNamespace; 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 * Internal class used to manage the server and the {@link HttpHandler}, taking care
* not to initialize the handler too early. * not to initialize the handler too early.
...@@ -195,11 +227,14 @@ public class ReactiveWebServerApplicationContext ...@@ -195,11 +227,14 @@ public class ReactiveWebServerApplicationContext
private final WebServer server; private final WebServer server;
private final boolean lazyInit;
private volatile HttpHandler handler; private volatile HttpHandler handler;
private ServerManager(ReactiveWebServerFactory factory) { private ServerManager(ReactiveWebServerFactory factory, boolean lazyInit) {
this.handler = this::handleUninitialized; this.handler = this::handleUninitialized;
this.server = factory.getWebServer(this); this.server = factory.getWebServer(this);
this.lazyInit = lazyInit;
} }
private Mono<Void> handleUninitialized(ServerHttpRequest request, private Mono<Void> handleUninitialized(ServerHttpRequest request,
...@@ -217,8 +252,9 @@ public class ReactiveWebServerApplicationContext ...@@ -217,8 +252,9 @@ public class ReactiveWebServerApplicationContext
return this.handler; return this.handler;
} }
public static ServerManager get(ReactiveWebServerFactory factory) { public static ServerManager get(ReactiveWebServerFactory factory,
return new ServerManager(factory); boolean lazyInit) {
return new ServerManager(factory, lazyInit);
} }
public static WebServer getWebServer(ServerManager manager) { public static WebServer getWebServer(ServerManager manager) {
...@@ -228,7 +264,9 @@ public class ReactiveWebServerApplicationContext ...@@ -228,7 +264,9 @@ public class ReactiveWebServerApplicationContext
public static void start(ServerManager manager, public static void start(ServerManager manager,
Supplier<HttpHandler> handlerSupplier) { Supplier<HttpHandler> handlerSupplier) {
if (manager != null && manager.server != null) { if (manager != null && manager.server != null) {
manager.handler = handlerSupplier.get(); manager.handler = manager.lazyInit
? new LazyHttpHandler(Mono.fromSupplier(handlerSupplier))
: handlerSupplier.get();
manager.server.start(); manager.server.start();
} }
} }
......
...@@ -218,6 +218,13 @@ ...@@ -218,6 +218,13 @@
"description": "Mode used to display the banner when the application runs.", "description": "Mode used to display the banner when the application runs.",
"defaultValue": "console" "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", "name": "spring.main.show-banner",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
......
...@@ -22,6 +22,7 @@ import java.util.Iterator; ...@@ -22,6 +22,7 @@ import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
...@@ -1171,6 +1172,21 @@ public class SpringApplicationTests { ...@@ -1171,6 +1172,21 @@ public class SpringApplicationTests {
.getBean("someBean")).isEqualTo("override"); .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( private Condition<ConfigurableEnvironment> matchingPropertySource(
final Class<?> propertySourceClass, final String name) { final Class<?> propertySourceClass, final String name) {
return new Condition<ConfigurableEnvironment>("has property source") { return new Condition<ConfigurableEnvironment>("has property source") {
...@@ -1437,6 +1453,29 @@ public class SpringApplicationTests { ...@@ -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 static class ExitStatusException extends RuntimeException
implements ExitCodeGenerator { 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 ...@@ -29,7 +29,7 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r
public class SampleWebFluxApplication { public class SampleWebFluxApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SampleWebFluxApplication.class); SpringApplication.run(SampleWebFluxApplication.class, args);
} }
@Bean @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