Lazy initialization in Spring Boot 2.2
From a translator: since the Spring Framework is one of the main frameworks on which we build CUBA , news about the new Spring features do not pass unnoticed by us. Lazy initialization is one way to reduce the time the application first loads, which in our age of the widespread use of microservices is an important metric. For those who prefer reading watching videos, there is a 10-minute presentation by Josh Long on the topic of the article.
The recently announced first milestone release of Spring Boot 2.2 adds support for lazy initialization. In this article we will look at new functionality and explain how to enable it.
What does it mean to be lazy?
The Spring Framework has been supporting lazy initialization since its source code moved to git eleven years ago. By default, when the application context is updated, each bean is recreated and its dependencies are implemented. In contrast, if a bean is configured for lazy initialization, it will not be created and its dependencies will not be put down until this is necessary.
Enabling lazy initialization
In any version of Spring Boot, it is possible to enable lazy initialization, if you do not mind getting your hands dirty with
BeanFactoryPostProcessor. Spring Boot 2.2 simply simplifies this process by introducing a new property -
spring.main.lazy-initialization(there are also equivalent methods in
SpringApplicationBuilder). When this property is set to
true, application beans will be configured to use lazy initialization.
The benefits of lazy initialization
Lazy initialization can significantly reduce the start time of your application, since at this stage fewer classes are loaded and fewer bins are created. For example, a small web application that uses Actuator and Spring Security usually starts 2.5 seconds. And with lazy initialization, this process takes 2 seconds. The exact acceleration values will vary from application to application, depending on the structure of the dependency graph of the bin.
Translator’s note: I ran this example , writing Spring Boot 2.2 in the dependencies, and the starting time with lazy initialization was 3 seconds, and without it 4. I think that on more serious applications, a significant gain in start time due to the use of lazy initialization we will not see. Upd: on the advice of alek_sys disabled the validation and updating of the database schema and turned on lazy JPA initialization for both cases - it turned out 2.7 and 3.7 seconds before the inscription appears,
Started WebApplication in...respectively
What about DevTools?
Spring Boot DevTools provides significant development acceleration. Instead of restarting the JVM and application every time you change something, DevTools do a “hot restart” of the application in the same JVM. A significant advantage of such a restart is that it gives JIT the opportunity to optimize the code that executes when the application starts. After several restarts, the initial time of 2.5 seconds decreases by almost 80% to 500 ms. With lazy initialization, things are even better. Setting the property
spring.main.lazy-initializationshows the restart time directly in the IDE equal to 400 ms.
The flip side of lazy initialization
As shown above, the inclusion of lazy initialization can seriously reduce the launch time of the application. And perhaps you will have an irresistible desire to use this constantly or, at least, you will be wondering why lazy initialization is not enabled by default. There are several possible negative effects that are best clarified immediately.
The fact that classes are not loaded and bins are not created until they are needed can mask problems that could have been identified earlier at the application launch stage. For example, it may be the lack of the required class, memory overflow or an error associated with the wrong configuration.
In web applications, lazy configurations can increase the latency of HTTP requests that cause bin initialization. This is usually the first request, but there may be additional unwanted effects affecting load balancing or automatic scaling.
Is this thing included?
If you are not sure how exactly lazy initialization affects your application or if you want to check that other aspects of the framework are suitable for you and do what you need, then it will be useful for you to use a debugger for this. By setting a breakpoint on the bin constructor, you can see at what exact moment the bin is initialized. For example, in a web application written in Spring Boot and with lazy initialization enabled, you can see that beans marked with annotation
@Controllerare not created until the first request to
DispatcerServletSpring MVC or to
When to enable lazy initialization?
As we saw above, lazy initialization offers notable improvements during application launch, but there are also downsides, so you need to use this feature very carefully.
One area where lazy initialization can pay dividends (with almost no overhead) is the application development process. While you are writing an application, the reduced restart time provided by lazy initialization in combination with DevTools can save you a lot of time.
Where else can you get the benefits of using lazy initialization - this is in integration tests. You may already be using test slicing to reduce execution time by limiting the number of initialized beans in some types of tests. Lazy initialization provides an alternative opportunity to achieve the same result. If you are in the wrong position to change the structure of the application for “slicing” tests, or for your specific tests there is no suitable “slicing”, then the inclusion of lazy initialization will limit the number of bins to those that are used only in your test. This will reduce test execution time, especially if they run in an isolated environment during development.
Turn on lazy initialization on the prod last. And, if you decide to do it, do it with caution. For web applications, the container manager can rely on an entry point
/health, which usually answers pretty quickly, but keep in mind that, potentially, the first calls may take longer than usual. You must also remember the memory size allocated for the JVM so that you do not encounter overflow when all components are initialized.