Spring/SpringBoot--延迟加载/懒加载/延迟初始化/@Lazy注解--使用/原理

以你之姓@ 2022-09-12 12:46 1622阅读 0赞

原文网址:Spring/SpringBoot—延迟加载/懒加载/延迟初始化/@Lazy注解—使用/原理_IT利刃出鞘的博客-CSDN博客

简介

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

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

延迟加载用法

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

  1. @Lazy
  2. @Component
  3. public class XXXX {
  4. ...
  5. }

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

  1. @Configuration
  2. public class XXXX {
  3. @Lazy
  4. @Bean
  5. public XXX getXXX() {
  6. return new XXX();
  7. }
  8. }

法3:@ComponentScan配置

  1. @ComponentScan(value = "XXX.XXX", lazyInit = true)
  2. @Configuration
  3. public class XXXX {
  4. ...
  5. }

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

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

全局与局部延迟初始化

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

法1:application.yml

  1. spring.main.lazy-initialization=true

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

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

法3:主程序开启

  1. @SpringBootApplication
  2. public class DemoSpringbootApplication {
  3. @Lazy
  4. public static void main(String[] args) {
  5. SpringApplication sa = new SpringApplication(DemoSpringbootApplication.class);
  6. sa.setLazyInitialization(true);
  7. sa.run(args);
  8. }
  9. }

法4:主程序开启

  1. @SpringBootApplication
  2. public class DemoSpringbootApplication {
  3. @Lazy
  4. public static void main(String[] args) {
  5. SpringApplicationBuilder sab = new SpringApplicationBuilder(DemoSpringbootApplication.class);
  6. sab.lazyInitialization(true).run(args);
  7. }
  8. }

简单示例

Bean

  1. package com.example.lazy;
  2. import org.springframework.context.annotation.Lazy;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. @Lazy
  6. public class LazyTest {
  7. public LazyTest() {
  8. System.out.println("[LazyTest.LazyTest]: 10");
  9. }
  10. public void print() {
  11. System.out.println("This is LazyTest print");
  12. }
  13. }

ApplicationContext工具类

  1. package com.example.lazy;
  2. import org.springframework.beans.BeansException;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.ApplicationContextAware;
  5. import org.springframework.stereotype.Component;
  6. @Component
  7. public class SpringApplicationContextHolder implements ApplicationContextAware {
  8. private static ApplicationContext context;
  9. public void setApplicationContext(ApplicationContext context)
  10. throws BeansException {
  11. SpringApplicationContextHolder.context = context;
  12. }
  13. public static ApplicationContext getContext() {
  14. return context;
  15. }
  16. }

测试类

  1. package com.example.controller;
  2. import com.example.lazy.LazyTest;
  3. import com.example.lazy.SpringApplicationContextHolder;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. @RestController
  7. public class HelloController {
  8. @GetMapping("/test1")
  9. public String test1() {
  10. LazyTest lazyTest = SpringApplicationContextHolder.getContext().getBean(LazyTest.class);
  11. System.out.println("即将执行lazy类的方法");
  12. lazyTest.print();
  13. return "test1 success";
  14. }
  15. }

测试

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

访问:localhost:8080/test1

结果:

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

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

  1. [LazyTest.LazyTest]: 10

使用场景

1.解决循环依赖

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

失效示例

非延迟加载的Controller

  1. @Controller
  2. public class TestController implements InitializingBean{
  3. @Autowired
  4. private TestService testService;
  5. @Override
  6. public void afterPropertiesSet() throws Exception {
  7. System.out.println("testController Initializing");
  8. }
  9. }

延迟加载的Service

  1. @Lazy
  2. @Service
  3. public class TestService implements InitializingBean {
  4. @Override
  5. public void afterPropertiesSet() throws Exception {
  6. System.out.println("testService Initializing");
  7. }
  8. }

测试

启动打印:

  1. testService Initializing
  2. testController Initializing

分析

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

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

解决方法:

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

  1. @Lazy
  2. @Controller
  3. public class TestController implements InitializingBean{
  4. @Autowired
  5. private TestService testService;
  6. @Override
  7. public void afterPropertiesSet() throws Exception {
  8. System.out.println("testController Initializing");
  9. }
  10. }

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

  1. @Controller
  2. public class TestController implements InitializingBean{
  3. @Autowired
  4. @Lazy
  5. private TestService testService;
  6. @Override
  7. public void afterPropertiesSet() throws Exception {
  8. System.out.println("testController Initializing");
  9. }
  10. }

原理

简介

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

Spring 初始化入口 refresh

  1. public void refresh() throws BeansException, IllegalStateException {
  2. synchronized (this.startupShutdownMonitor) {
  3. // Prepare this context for refreshing.
  4. prepareRefresh();
  5. // Prepare the bean factory for use in this context.
  6. prepareBeanFactory(beanFactory);
  7. try {
  8. // Allows post-processing of the bean factory in context subclasses.
  9. postProcessBeanFactory(beanFactory);
  10. // Invoke factory processors registered as beans in the context.
  11. invokeBeanFactoryPostProcessors(beanFactory);
  12. // Register bean processors that intercept bean creation.
  13. registerBeanPostProcessors(beanFactory);
  14. // Instantiate all remaining (non-lazy-init) singletons.
  15. // 初始化所有非懒加载的bean
  16. finishBeanFactoryInitialization(beanFactory);
  17. // Last step: publish corresponding event.
  18. finishRefresh();
  19. }catch(){}
  20. }
  21. }

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

finishBeanFactoryInitialization(beanFactory)

  1. public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
  2. protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
  3. //其他代码
  4. // 本处关注的代码
  5. beanFactory.preInstantiateSingletons();
  6. }
  7. // 其他代码
  8. }

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

preInstantiateSingletons

具体逻辑如下

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

  1. public void preInstantiateSingletons() throws BeansException {
  2. // Iterate over a copy to allow for init methods which in turn register new bean definitions.
  3. // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
  4. // 所有beanDefinition集合
  5. List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
  6. // Trigger initialization of all non-lazy singleton beans...
  7. // 触发所有非懒加载单例bean的初始化
  8. for (String beanName : beanNames) {
  9. // 获取bean 定义
  10. RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
  11. // 判断是否是懒加载单例bean。如果是单例的并且不是懒加载的则在Spring 容器
  12. if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
  13. // 判断是否是FactoryBean
  14. if (isFactoryBean(beanName)) {
  15. Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
  16. if (bean instanceof FactoryBean) {
  17. final FactoryBean<?> factory = (FactoryBean<?>) bean;
  18. boolean isEagerInit;
  19. if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
  20. isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
  21. ((SmartFactoryBean<?>) factory)::isEagerInit,
  22. getAccessControlContext());
  23. }
  24. else {
  25. isEagerInit = (factory instanceof SmartFactoryBean &&
  26. ((SmartFactoryBean<?>) factory).isEagerInit());
  27. }
  28. if (isEagerInit) {
  29. getBean(beanName);
  30. }
  31. }
  32. }
  33. else {
  34. // 如果是普通bean则进行初始化依赖注入,此 getBean(beanName)接下来触发的逻辑跟
  35. // context.getBean("beanName") 所触发的逻辑是一样的
  36. getBean(beanName);
  37. }
  38. }
  39. }
  40. }

发表评论

表情:
评论列表 (有 0 条评论,1622人围观)

还没有评论,来说两句吧...

相关阅读

    相关 jQuery延迟

    简介 延迟加载(lazy load)又可称之为懒加载,他是为了避免一些无谓的性能开销而提出来的。所谓延迟加载,就是当真正需要数据的时候,才真正执行数据加载操作。可简单理解为