所有分类
  • 所有分类
  • 未分类

Spring–延迟加载(@Lazy注解等)–使用/原理

简介

本文介绍Spring中Bean的生命周期–延迟初始化。

  • 延迟初始化通常又被称为“懒加载”。
  • 延迟初始化定义:在启动时不初始化Bean,直到用到这个Bean的时候才去初始化。
  • 默认情况下,Bean在启动时进行初始化。

延迟加载用法

局部延迟初始化

法1:在@Component类上加上@Lazy注解

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

法2:@Configuration类中配置@Bean时添加@Lazy注解

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

 法3:@ComponentScan配置

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

法4:在XML文件中直接配置标签属性

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

 全局延迟初始化

上边这些配置方法都是局部延迟初始化,全局配置延迟初始化的方法如下:

法1:application.yml

spring.main.lazy-initialization=true

法2:在XML文件中直接配置标签属性

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

法3:主程序开启

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

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

法4:主程序开启

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

简单示例

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工具类

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;
    }
}

测试类

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("即将执行lazy类的方法");
        lazyTest.print();

        return "test1 success";
    }
}

测试

启动程序。结果:无相关打印。

访问:localhost:8080/test1

结果:

[LazyTest.LazyTest]: 10
即将执行lazy类的方法
This is LazyTest print

 如果将LazyTest类上的@Lazy去掉,则一启动,就会打印构造函数中的内容:

[LazyTest.LazyTest]: 10

使用场景

1.解决循环依赖

2.解决在@Configuration中注入@Service注解的类

失效示例

非延迟加载的Controller

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

延迟加载的Service

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

测试

启动打印:

testService Initializing
testController Initializing

分析

启动完Spring程序后输出了TestService里面打印的字符串。明明使用了@Lazy注解,却没有其作用,在Spring启动项目时还是加载了这个类。这就涉及到@Autowired等自动注入注解的使用了。

由于Controller类不是延迟加载的,且里面使用@Autowired自动注入注解注入了Service,因此在程序初始化时Controller将会被初始化,同时在处理@Autowired注解的字段时,会调用getBean方法从Spring工厂中获取字段的bean对象,因此通过@Autowired路线加在了Service,这就导致了@Lazy注解失效了,因此虽然没通过refresh方法流程初始化,但是却通过@Autowired的处理类初始化了。

解决方法:

法1:把Controller也改成@Lazy,让其在启动时不被加载,不触发@Autowired注解依赖链的调用即可。如下:

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

法2:@Autowired的地方加上@Lazy。如下:

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

原理

简介

容器启动的时候 只处理 non-lazy-init bean,懒加载的bean在Spring启动阶段根本不做任何处理。

Spring 初始化入口 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.
            // 初始化所有非懒加载的bean
            finishBeanFactoryInitialization(beanFactory);

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

finishBeanFactoryInitialization(beanFactory);是跟本次主题有关的。

finishBeanFactoryInitialization(beanFactory)

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        //其他代码
        
        // 本处关注的代码
        beanFactory.preInstantiateSingletons();
    }
    // 其他代码
}

finishBeanFactoryInitialization(beanFactory)里头有个初始化non-lazy-init bean的函数 preInstantiateSingletons()

preInstantiateSingletons

具体逻辑如下

1.对beanNames 集合遍历获取每个BeanDefinition
2.判断是否是懒加载的,如果不是则继续处理(non-lazy-init bean 不做处理)
3.判断是否是factorybean 如果不是则进行实例化并依赖注入

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.
    // 所有beanDefinition集合
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

    // Trigger initialization of all non-lazy singleton beans...
    // 触发所有非懒加载单例bean的初始化
    for (String beanName : beanNames) {
        // 获取bean 定义
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        // 判断是否是懒加载单例bean。如果是单例的并且不是懒加载的则在Spring 容器
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            // 判断是否是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 {
                // 如果是普通bean则进行初始化依赖注入,此 getBean(beanName)接下来触发的逻辑跟
                // context.getBean("beanName") 所触发的逻辑是一样的
                getBean(beanName);
            }
        }
    }
}
0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录