Spring中Singleton构造函数调用次数及同IDBean多次调用场景问询
刚好之前在项目里踩过相关的坑,来给你详细唠唠这个问题:
一、正常情况下的调用次数
对于Spring容器中的Singleton(单例)Bean,默认只会调用一次构造函数:
- 饿汉式加载(Spring默认行为):容器启动时就会创建Bean实例,构造函数执行一次;
- 懒加载(通过
@Lazy或XML的lazy-init="true"配置):第一次通过容器获取Bean时创建实例,构造函数同样只执行一次。
之后无论你多少次调用getBean()获取这个Bean,容器都会复用已创建的实例,不会再触发构造函数。
二、导致同ID Bean构造函数多次调用的常见场景
下面这些都是实际开发中容易碰到的情况,我给你逐一拆解:
1. 同一个Bean被多个Spring容器加载
在Web应用里很常见——Spring的根容器(ApplicationContext)和MVC的Servlet容器(DispatcherServlet对应的容器)是父子容器关系。如果同一个Bean的定义(比如@Component扫描路径重复、XML配置重复)同时被两个容器加载,每个容器都会独立创建这个Bean的实例,构造函数自然会被调用多次。
举个例子:你把UserService放在了同时被根容器和MVC容器扫描的包下,根容器会实例化一次UserService,MVC容器又会再实例化一次,构造函数就执行了两次。
2. 容器被手动/自动刷新
当你手动调用ApplicationContext.refresh()方法,或者在Spring Cloud这类场景中,配置中心触发上下文自动刷新时,Spring容器会销毁原有所有单例Bean,然后重新实例化所有Bean。这时候每个单例Bean的构造函数都会被再次调用。
比如用了Spring Cloud Config,当配置文件更新触发上下文刷新,所有单例Bean都会被重建,构造函数的执行次数会对应增加。
3. 使用@RefreshScope注解
@RefreshScope是Spring Cloud提供的特殊Scope,用来实现配置动态刷新。它本质上是一种"懒加载的单例":当配置刷新时,容器会标记这个Bean为过期,下次获取该Bean时,会销毁旧实例并创建新实例,这时候构造函数就会被再次调用。
如果你给一个单例Bean加了@RefreshScope,每次配置刷新后第一次获取Bean,都会触发构造函数执行。
4. 重复定义同ID Bean且允许覆盖
默认情况下,Spring容器不允许同一个ID的Bean被重复定义(会抛出BeanDefinitionStoreException),但如果你通过设置spring.main.allow-bean-definition-overriding=true(Spring Boot)或者XML的allow-bean-definition-overriding="true"开启了覆盖允许,后面定义的Bean会覆盖前面的。
如果前面的Bean已经被实例化(比如饿汉式加载),那么前面的构造函数已经执行了一次,后面覆盖时又会创建新的实例,构造函数再次执行,导致两次调用。
5. 手动注册同ID Bean多次
如果你自定义了BeanDefinitionRegistryPostProcessor,在这个后置处理器里多次向容器注册同一个ID的Bean定义,并且开启了Bean覆盖允许,那么每次注册如果触发实例化(比如饿汉式),都会导致构造函数被调用多次。
比如在后置处理器里循环注册3次同ID的Bean,构造函数就会执行3次(每次注册覆盖前一个,同时实例化新的)。
6. 使用FactoryBean且getObject()返回新实例
如果你的Bean是通过FactoryBean实现的,FactoryBean本身是单例,但getObject()方法每次都返回一个新的Bean实例(比如方法里直接new对象),那么每次获取这个Bean时,都会调用目标Bean的构造函数,导致多次调用。这种情况容易被忽略,因为FactoryBean的ID对应的是返回的Bean,看起来就是同ID的Bean被多次创建。
内容的提问来源于stack exchange,提问作者ByeBye

