延迟初始化Bean会影响依赖注入吗

简介: 延迟初始化Bean会影响依赖注入吗

前言

大家好,我是java小面,今天我们继续前面Spring文章比较核心的Bean内容的探讨,这次来探讨的是关于延迟初始化Bean是否会影响到依赖注入的问题,依赖注入一直以来都是Spring面试中的核心,很多面试官都很喜欢围绕着依赖注入和依赖查找去考察面试人对Spring的理解深度以使用情况。

Bean延迟初始化(Lazy Initialization)

它的使用很简单,可以通过xml来配置和Java 注解@Lazy来为Bean的初始化进行配置。

那么问题来了,当某个Bean被定义为延迟初始化,那么当我们依赖注入拿到时,延迟和非延迟对象之间存在着什么差异呢?

案例分析

/**
 * @author Java面试教程
 * @date 2022-12-18 21:14
 */
@Configuration
public class Demo {
    public static void main(String[] args)
    {
        //创建BeanFactory容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        //注册当前类,主要目的是获取@Bean
        applicationContext.register(Demo.class);
        //启动应用上下文
        applicationContext.refresh();
        System.out.println("Spring应用上下文已启动...");
        //依赖查找
        UserFactory bean = applicationContext.getBean(UserFactory.class);
        //关闭应用上下文
        applicationContext.close();
    }
    @Bean(initMethod = "initUserFactory")
    @Lazy
    public UserFactory createUserFactory(){
        System.out.println("初始化了");
        return new DefaultUserFactory();
    }
}

我们在createUserFactory这个方法上打上@Lazy注解,来标记它是一个延迟初始化。

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
   /**
    * Whether lazy initialization should occur.
    */
   boolean value() default true;
}

我们看一下关于这个@Lazy注解,我们可以看到这么一个关键字,default true ,当value=true的时候默认值是延迟加载的意思。

boolean value() default true;

学到这里,小面的脑子里就又冒出了一个疑问了,Java注解是一个静态的东西,一旦标记上了,除非重新编译打包,不然没办法修改才对,与其要我标上@Lazy特地打个false,那还不如一开始不打上这个注解呢?可能有什么其他意义吧?小面还没学到那个地步,真的是小小的脑袋,大大的疑问,如果有懂这方面的大佬,不妨在群里告诉一下撒。

略过这个疑问,我们来运行一下这个Main方法看看两者的区别

这是使用了@Lazy(value = true)的时候

Connected to the target VM, address: '127.0.0.1:51636', transport: 'socket'
Spring应用上下文已启动...
初始化了
org.example.factory.DefaultUserFactory@2e222612
Disconnected from the target VM, address: '127.0.0.1:51636', transport: 'socket'

这是使用了@Lazy(value = false)的时候

Connected to the target VM, address: '127.0.0.1:51636', transport: 'socket'
初始化了
Spring应用上下文已启动...
org.example.factory.DefaultUserFactory@77eca502
Disconnected from the target VM, address: '127.0.0.1:51636', transport: 'socket'

这是我使用了@Lazy(value = true) 且把依赖查找注释掉的时候

//        UserFactory bean = applicationContext.getBean(UserFactory.class);
//        System.out.println(bean);

我们看看它打印了什么

Connected to the target VM, address: '127.0.0.1:51636', transport: 'socket'
Spring应用上下文已启动...
Disconnected from the target VM, address: '127.0.0.1:51636', transport: 'socket'

这样子,两者的使用区别是不是就很明显了,说明使用了@Lazy的时候,代表我这个Bean是按需初始化,我需要使用它的时候才会去初始化它。

这就是它们两者的主要区别。

继续学习

我们来看看它在实现上是不是有什么不一样?我们来看一下refresh方法

//启动应用上下文
applicationContext.refresh();

它有这么核心的一段代码

// 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);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();

其中我们看 finishBeanFactoryInitialization(beanFactory) 按照方法的名称的意思就叫做完成BeanFactory的初始化

我们看看这句注解 ”Instantiate all remaining (non-lazy-init) singletons“ 它的意思大概是,它会去初始化所有非延迟初始化的单体类或者Bean。

我们看一下这个方法的实现

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   // Initialize conversion service for this context.
   if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
         beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
      beanFactory.setConversionService(
            beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
   }
   // Register a default embedded value resolver if no bean post-processor
   // 如果没有bean后处理器,则注册一个默认的嵌入式值解析器(例如PropertyPlaceholderConfigurer bean)注册任何之前:
   // (such as a PropertyPlaceholderConfigurer bean) registered any before:
   // at this point, primarily for resolution in annotation attribute values.
   if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
   }
   // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
   String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
   for (String weaverAwareName : weaverAwareNames) {
      getBean(weaverAwareName);
   }
   // Stop using the temporary ClassLoader for type matching.
   beanFactory.setTempClassLoader(null);
   // Allow for caching all bean definition metadata, not expecting further changes.
   beanFactory.freezeConfiguration();
   // Instantiate all remaining (non-lazy-init) singletons.
   // 实例化所有剩余的(非lazy-init)单例
   beanFactory.preInstantiateSingletons();
}

圈重点看这一句,它的意思是去初始化剩余的东西,

// Instantiate all remaining (non-lazy-init) singletons.
// 实例化所有剩余的(非lazy-init)单例
beanFactory.preInstantiateSingletons();

是否意味着在应用文上下启动的时候,有这么一个前置动作,执行了什么把需要初始化的Bean分了类,然后导致标识为正常初始化,非lazy-init的类或对象被定义成了剩余的单例。

总结

通过源码的深入,我们其实可以看出,延迟加载和非延迟加载在定义的时候,Bean注册的时候是没有区别的,在依赖查找和依赖注入的时候就明显不同了,非延迟是在上下文启动之前就初始化Bean了,而延迟是在Bean初始化之后按需加载。

相关文章
|
存储 API 网络性能优化
OpenStack的块存储卷管理
【8月更文挑战第25天】
325 4
|
7月前
|
存储 监控 算法
基于 C# 时间轮算法的控制局域网上网时间与实践应用
在数字化办公与教育环境中,局域网作为内部网络通信的核心基础设施,其精细化管理水平直接影响网络资源的合理配置与使用效能。对局域网用户上网时间的有效管控,已成为企业、教育机构等组织的重要管理需求。这一需求不仅旨在提升员工工作效率、规范学生网络使用行为,更是优化网络带宽资源分配的关键举措。时间轮算法作为一种经典的定时任务管理机制,在局域网用户上网时间管控场景中展现出显著的技术优势。本文将系统阐述时间轮算法的核心原理,并基于 C# 编程语言提供具体实现方案,以期深入剖析该算法在局域网管理中的应用逻辑与实践价值。
207 5
|
11月前
|
数据挖掘 测试技术 项目管理
2025年测试用例管理看这一篇就够了 ----Codes 开源免费、全面的测试管理解决方案
Codes 是国内首款重新定义 SaaS 模式的开源项目管理平台,支持云端认证、本地部署、全部功能开放,并且对 30 人以下团队免费。它通过整合迭代、看板、度量和自动化等功能,简化测试协同工作,使敏捷测试更易于实施。并提供低成本的敏捷测试解决方案,如同步在线离线测试用例、流程化管理缺陷、低代码接口自动化测试和 CI/CD,以及基于迭代的测试管理和测试用时的成本计算等,践行敏捷测试。
2025年测试用例管理看这一篇就够了 ----Codes 开源免费、全面的测试管理解决方案
|
安全 关系型数据库 MySQL
Centos、OpenEuler系统安装mysql
Centos、OpenEuler系统安装mysql
542 1
|
存储 监控 NoSQL
redis主从模式,redis哨兵模式,redis集群模式
redis主从模式,redis哨兵模式,redis集群模式
416 1
redis主从模式,redis哨兵模式,redis集群模式
|
算法 UED 异构计算
性能优化在嵌入式系统中的应用
性能优化在嵌入式系统中的应用
300 3
|
SQL 关系型数据库 MySQL
(十八)MySQL排查篇:该如何定位并解决线上突发的Bug与疑难杂症?
前面《MySQL优化篇》、《SQL优化篇》两章中,聊到了关于数据库性能优化的话题,而本文则再来聊一聊关于MySQL线上排查方面的话题。线上排查、性能优化等内容是面试过程中的“常客”,而对于线上遇到的“疑难杂症”,需要通过理性的思维去分析问题、排查问题、定位问题,最后再着手解决问题,同时,如果解决掉所遇到的问题或瓶颈后,也可以在能力范围之内尝试最优解以及适当考虑拓展性。
1234 3
|
Java 数据库连接 开发者
深入理解Spring Boot中的@Bean注解
【4月更文挑战第22天】在 Spring Boot 应用开发中,@Bean 注解是一种非常重要的方法,用于在配置类中声明单个 Bean,从而使 Spring 容器能够管理这些 Bean。本篇技术博客将详细解析 @Bean 注解的概念,并通过具体的实战示例展示如何有效地使用这一注解优化应用的配置和管理
1396 5
|
存储 调度 数据库
Quartz.NET开源作业调度框架系列(三):IJobExecutionContext 参数传递
在Quartz.NET中可以用JobDataMap进行参数传递。本例用Quartz.NET的任务来定期轮询数据库表,当数据库的条目达到一定的数目后,进行预警。
1424 0
Quartz.NET开源作业调度框架系列(三):IJobExecutionContext 参数传递
|
消息中间件 安全 Java
Java中的异步编程方案总结
Java中的异步编程是一种能够提高程序性能和响应速度的技术。它通过将耗时的操作放在单独的线程中,让主线程继续执行其他任务,从而实现并发处理和异步执行。在Java中,异步编程常用的方式有多线程、Future和CompletableFuture等。在实际应用中,异步编程可以优化网络请求、数据库操作等IO密集型任务的性能,提高程序的响应速度和吞吐量。虽然异步编程可以带来许多好处,但同时也涉及到一些问题,比如线程安全、回调地狱等。因此,在使用异步编程时需要注意合理地设计和管理线程,确保程序的正确性和可维护性。
946 1
Java中的异步编程方案总结