Spring/SpringBoot -- deferred loading / Lazy loading / deferred initialization / @ Lazy annotation -- use / usage / principle

Posted by H4mm3r3r on Sat, 25 Sep 2021 03:34:55 +0200

Original website: Spring/SpringBoot -- deferred loading / Lazy loading / deferred initialization / @ Lazy annotation -- use / usage / principle_ CSDN blog

brief introduction

explain

  • Delayed initialization is often referred to as "lazy loading".
  • Definition of delayed initialization: a Bean is not initialized at startup and is not initialized until it is used.
  • By default, beans are initialized at startup.

Deferred load usage

Method 1: add @ Lazy annotation on @ Component class

@Lazy
@Component
public class XXXX {
    ...
}

Method 2: @ Lazy annotation is added when @ Bean is configured in @ Configuration class

@Configuration
public class XXXX {
    @Lazy
    @Bean
    public XXX getXXX() {
        return new XXX();
    }
}

  Method 3: @ ComponentScan configuration

@ComponentScan(value = "XXX.XXX", lazyInit = true)
@Configuration
public class XXXX {
    ...
}

Method 4: directly configure the tag attribute in the XML file

<bean id="XXX" class="XXX.XXX.XXXX" lazy-init="true"/>

Global and local delay initialization

The above configuration methods are local delay initialization, and the global configuration delay initialization methods are as follows:

Method 1: application.yml

spring.main.lazy-initialization=true

Method 2: directly configure the tag attribute in the XML file

<beans ... default-lazy-init="true"/>

Method 3: start the main program

@SpringBootApplication
public class DemoSpringbootApplication {
    @Lazy
    public static void main(String[] args) {
        SpringApplication sa = new SpringApplication(DemoSpringbootApplication.class);

        sa.setLazyInitialization(true);
        sa.run(args);
    }
}

Method 4: start the main program

@SpringBootApplication
public class DemoSpringbootApplication {
    @Lazy
    public static void main(String[] args) {
        SpringApplicationBuilder sab = new SpringApplicationBuilder(DemoSpringbootApplication.class);
        
        sab.lazyInitialization(true).run(args);
    }
}

Simple example

Bean

package com.example.lazy;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
@Lazy
public class LazyTest {
    public LazyTest() {
        System.out.println("[LazyTest.LazyTest]: 10");
    }

    public void print() {
        System.out.println("This is LazyTest print");
    }
}

ApplicationContext tool class

package com.example.lazy;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
 
@Component
public class SpringApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;
 
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        SpringApplicationContextHolder.context = context;
    }
 
    public static ApplicationContext getContext() {
        return context;
    }
}

Test class

package com.example.controller;

import com.example.lazy.LazyTest;
import com.example.lazy.SpringApplicationContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/test1")
    public String test1() {
        LazyTest lazyTest = SpringApplicationContextHolder.getContext().getBean(LazyTest.class);
        System.out.println("About to execute lazy Class method");
        lazyTest.print();

        return "test1 success";
    }
}

test

Start the program. Result: no related printing.

visit: localhost:8080/test1

result:

[LazyTest.LazyTest]: 10
 About to execute lazy Class method
This is LazyTest print

  If @ Lazy on the LazyTest class is removed, the contents of the constructor will be printed as soon as it is started:

[LazyTest.LazyTest]: 10

Usage scenario

1. Solve circular dependency

2. Solve the problem of injecting @ Service annotation into @ Configuration

Failure example

Non deferred loaded Controller

@Controller
public class TestController implements InitializingBean{
    @Autowired
    private TestService testService;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
}

Deferred loaded Service

@Lazy
@Service
public class TestService implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testService Initializing");
    }
}

test

Start printing:

testService Initializing
testController Initializing

analysis

        After starting the Spring program, the string printed in TestService is output. The @ Lazy annotation is clearly used, but it has no effect. This class is still loaded when Spring starts the project. This involves the use of automatic injection annotations such as @ Autowired.

        Since the Controller class is not loaded late and uses the @ Autowired auto injection annotation to inject Service, the Controller will be initialized during program initialization. At the same time, when processing the @ Autowired annotated fields, it will call the getBean method to obtain the bean object of the field from the Spring factory. Therefore, it is added to the Service through the @ Autowired route, This leads to the invalidation of @ Lazy annotation. Therefore, although it is not initialized through the refresh method process, it is initialized through the processing class of @ Autowired.

resolvent:

Method 1: change the Controller to @ Lazy, so that it will not be loaded at startup and the call of @ Autowired annotation dependency chain will not be triggered. As follows:

@Lazy
@Controller
public class TestController implements InitializingBean{
    @Autowired
    private TestService testService;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
}

Method 2: @ Lazy is added to @ Autowired. As follows:

@Controller
public class TestController implements InitializingBean{
    @Autowired
    @Lazy
    private TestService testService;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("testController Initializing");
    }
}

principle

brief introduction

When the container starts   Only processing   Non lazy init beans, lazy loaded beans do not do any processing at all during the Spring startup phase.

Spring initialization entry refresh

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            // Instantiate all remaining (non-lazy-init) singletons.
            // Initialize all non lazy loaded bean s
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }catch(){}
    }
}

finishBeanFactoryInitialization(beanFactory); It is related to this topic.

finishBeanFactoryInitialization(beanFactory)

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        //Other codes
        
        // Code of concern
        beanFactory.preInstantiateSingletons();
    }
    // Other codes
}

There is a function to initialize non lazy init beans in finishBeanFactoryInitialization(beanFactory)   preInstantiateSingletons()

preInstantiateSingletons

The specific logic is as follows

1. Traverse the beanNames collection to obtain each BeanDefinition
2. Judge whether it is lazy loading. If not, continue processing (non lazy init beans do not process it)
3. Judge whether it is a factorybean. If not, instantiate and dependency injection

public void preInstantiateSingletons() throws BeansException {
    // Iterate over a copy to allow for init methods which in turn register new bean definitions.
    // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
    // All beanDefinition sets
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

    // Trigger initialization of all non-lazy singleton beans...
    // Trigger the initialization of all non lazy load singleton bean s
    for (String beanName : beanNames) {
        // Get bean definition
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        // Determine whether it is a lazy loading singleton bean. If it is singleton and not lazy loaded, it is in the Spring container
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            // Determine whether it is a FactoryBean
            if (isFactoryBean(beanName)) {
                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                if (bean instanceof FactoryBean) {
                    final FactoryBean<?> factory = (FactoryBean<?>) bean;
                    boolean isEagerInit;
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
                                        ((SmartFactoryBean<?>) factory)::isEagerInit,
                                getAccessControlContext());
                    }
                    else {
                        isEagerInit = (factory instanceof SmartFactoryBean &&
                                ((SmartFactoryBean<?>) factory).isEagerInit());
                    }
                    if (isEagerInit) {
                        getBean(beanName);
                    }
                }
            }
            else {
                // If it is an ordinary bean, initialization dependency injection is performed. The logic triggered by this getBean(beanName) is the same as
                // The logic triggered by context.getBean("beanName") is the same
                getBean(beanName);
            }
        }
    }
}

Topics: Java Spring