Spring获取单例bean方法的思考

本文深入探讨了Spring如何通过DefaultSingletonBeanRegistry获取单例bean,特别是涉及循环依赖的解决策略。分析了三级缓存(singletonObjects、singletonFactories、earlySingletonObjects)在处理循环依赖中的作用,并解释了为什么需要早期单例对象集合。文章还讨论了@Autowired注解在构造函数和setter方法中解决循环依赖的方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring获取单例bean方法的思考

使用的SpringMVC版本是5.1.8.RELEASE

下面是DefaultSingletonBeanRegistry类中通过类名获取单例的源码


/** 单例对象的缓存:从bean名称到bean实例 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** 单例对象的缓存:从bean名称到objects实例工厂 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** 早期单例对象的缓存:从bean名称到bean实例 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/**
 * 返回以给定名称注册的(原始)singleton对象
 * <p> 检查已经实例化了singleton,还允许对当前创建的singleton进行早期引用(解析循环引用)
 * @param beanName 要查找的bean的名称
 * @param allowEarlyReference 是否应创建早期引用
 * @return 已注册的singleton对象,或者如果未找到任何对象返回null
 */
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		synchronized (this.singletonObjects) {
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					singletonObject = singletonFactory.getObject();
					this.earlySingletonObjects.put(beanName, singletonObject);
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

/**
 * 返回一个指定的bean实例,可以是共享的,也可以是依赖的
 * @param name 要检索的bean的名称
 * @param requiredType 需要检索的bean类型
 * @param args 使用显式参数创建bean实例时要使用的参数
 * (仅在创建新实例而不是检索现有实例时应用)
 * @param typeCheckOnly 是否获取实例进行类型检查,而不是实际使用
 * @return bean实例
 * @throws BeansException 当bean实例无法创建时抛出
 */
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
		@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

	final String beanName = transformedBeanName(name);
	Object bean;

	// Eagerly check singleton cache for manually registered singletons.
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		// 忽略...
	}

	else {
		// 忽略...
		try {
			final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
			checkMergedBeanDefinition(mbd, beanName, args);

			// 保证当前bean所依赖的bean的初始化.
			String[] dependsOn = mbd.getDependsOn();
			if (dependsOn != null) {
				for (String dep : dependsOn) {
					if (isDependent(beanName, dep)) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
					}
					registerDependentBean(dep, beanName);
					try {
						getBean(dep);
					}
					catch (NoSuchBeanDefinitionException ex) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
					}
				}
			}

			// Create bean instance.
			if (mbd.isSingleton()) {
				// 核心方法:getSingleton(String beanName, ObjectFactory<?> singletonFactory)
				sharedInstance = getSingleton(beanName, () -> {
					try {
						return createBean(beanName, mbd, args);
					}
					catch (BeansException ex) {
						// Explicitly remove instance from singleton cache: It might have been put there
						// eagerly by the creation process, to allow for circular reference resolution.
						// Also remove any beans that received a temporary reference to the bean.
						destroySingleton(beanName);
						throw ex;
					}
				});
				bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
			}

			else if (mbd.isPrototype()) {
				// 忽略...
			}

			else {
				// 忽略...
			}
		}
		catch (BeansException ex) {
			cleanupAfterBeanCreationFailure(beanName);
			throw ex;
		}
	}

	// 忽略...
	return (T) bean;
}

/**
 * 获取给定bean实例的对象,如果是factorybean,则为bean实例本身或其创建的对象
 * @param beanInstance 共享的bean实例
 * @param name 可能包含工厂取消引用前缀的名称
 * @param beanName 标准bean名称
 * @param mbd 合并的bean定义
 * @return 为bean暴露的对象
 */
protected Object getObjectForBeanInstance(
		Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

	// Don't let calling code try to dereference the factory if the bean isn't a factory.
	if (BeanFactoryUtils.isFactoryDereference(name)) {
		if (beanInstance instanceof NullBean) {
			return beanInstance;
		}
		if (!(beanInstance instanceof FactoryBean)) {
			throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
		}
	}

	// Now we have the bean instance, which may be a normal bean or a FactoryBean.
	// If it's a FactoryBean, we use it to create a bean instance, unless the
	// caller actually wants a reference to the factory.
	if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
		return beanInstance;
	}

	Object object = null;
	if (mbd == null) {
		object = getCachedObjectForFactoryBean(beanName);
	}
	if (object == null) {
		// Return bean instance from factory.
		FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
		// Caches object obtained from FactoryBean if it is a singleton.
		if (mbd == null && containsBeanDefinition(beanName)) {
			mbd = getMergedLocalBeanDefinition(beanName);
		}
		boolean synthetic = (mbd != null && mbd.isSynthetic());
		object = getObjectFromFactoryBean(factory, beanName, !synthetic);
	}
	return object;
}


/**
 * 将给定的singleton对象添加到此工厂的singleton缓存中
 * <p> 在需要注册单例时被调用.
 * @param beanName bean的名称
 * @param singletonObject 单例对象
 */
protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName); // 无论如何都会在这里释放它,不管它是否已经转移到earlySingletonObjects
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}
  • 对象间的依赖
  1. 构造函数传参
public class TestBean1 {
	private TestBean2 testBean2;
	TestBean2(){}
	TestBean2(TestBean2 testBean2){
		this.testBean2 = testBean2;
	}
}
  1. setter方法传参
public class TestBean1 {
	private TestBean2 testBean2;
	public void setTestBean2(TestBean2 testBean2){
		this.testBean2 = testBean2;
	}
	public void getTestBean2(){
		return this.testBean2;
	}
}
  • 循环依赖问题
    循环依赖,bean之间相互持有各自的引用,最终形成闭环。

bean的实例化总共分三步:

  1. 调用构造函数实例化,分配内存空间,并对属性赋默认值;createBeanInstance
  2. 设置属性;populateBean
  3. 最终完成实例化;InitializeBean
  • 三级缓存解决循环依赖
    spring使用了三个Map类型的缓存变量:singletonObjects、singletonFactories、earlySingletonObjects来解决单例类实例化的循环依赖问题。但spring仍然无法解决构造器依赖和原型类依赖的问题。
    由于创建实例第一步就是执行构造函数,如果此时需要依赖于某个类的实例而spring又无法将当前正在实例化中的类提前曝光(还未获得堆内存的引用地址),则出现循环依赖异常;另外,spring三级缓存机制只针对类型为singleton的类,prototype类型需要每次单独创建实例,因此其属性值都需要显式调用setter来注入。

  • 构造器和setter函数上的@Autowired注解解决循环依赖
    在高版本的spring中已经可以支持构造函数和setter方式完成注入了,代码如下:

public class TestBean1 {
	private final TestBean2 testBean2;
	// 这种方式会导致构造器非常丑陋,没有在属性上直接添加注解来得简洁优雅
	@Autowired
	public TestBean1(TestBean2 testBean2){
		this.testBean2 = testBean2;
	}
}
public class TestBean1 {
	private TestBean2 testBean2;
	// 这种方式非常灵活,后期还可以进行重新注入,但代码量仍然不少
	@Autowired
	public setTestBean2(TestBean2 testBean2){
		this.testBean2 = testBean2;
	}
}
总结
  • 三级缓存解决循环依赖
    创建实例的过程,使用了三个Map类型的缓存变量:singletonObjects、singletonFactories、earlySingletonObjects ,其中单例对象集合使用线程安全的Map创建。通过getSingleton()方法的源码分析发现,获取bean实例时先从单例对象集合中找,再找早期单例集合,最后找单例工厂集合,如果从单例工厂中找到则将此bean实例从单例工厂集合中移除并放到早期单例集合中存储,那么bean实例什么时候进入的单例工厂集合,又是什么时候进入的单例对象集合中呢?
    通过doGetBean()方法可以定位到创建实例的方法正是getSingleton(String beanName, ObjectFactory<?> singletonFactory)的返回值,而且这个方法参数需要一个ObjectFactory的对象,该类添加了@FunctionalInterface注解,说明这是一个函数式接口,其getObject()返回的值正式通过createBean()方法创建出的bean实例。所以分三步读源码:
  1. 了解createBean创建一个实例的过程
  2. 了解getSingleton()方法在回调获取到bean实例后做了哪些处理
  3. 了解getObjectForBeanInstance()方法获取完整的实例对象

进而发现,创建实例时通过addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))将此实例(仅仅是个早期bean的引用)先保存到单例工厂集合内,创建实例完成之后通过addSingleton(beanName, singletonObject)方法将此实例保存到单例对象集合内,同时从单例工厂和早期单例集合中移除。
那么,将创建的实例临时性的保存在早期单例集合的是为什么呢?为什么呢?不知道

从设计理念上来说,singletonObjects持有的应该是完整的实例,singletonFactories是还在工厂进行生产的实例,如果此时需要被依赖,这两种实例都不合适,所以就需要增加一种可以提前将自己曝光(被依赖)的实例,所以就有了早期单例集合来保存它,同时如果早期单例集合已经持有了某个实例,单例工厂集合也就没必要再继续持有,可以直接释放(remove)掉,在创建实例的最后一步添加到单例对象集合中时也会释放它。

<think>嗯,用户这次的问题是关于Spring框架中@Bean注解创建的对象在注入后能否修改属性的。让我仔细梳理一下。从用户提供的引用内容来看,有几个关键点需要关注。引用1提到@Bean注解的initMethod和destroyMethod属性,引用2和3强调了Spring5.1之后autowire属性的弃用,引用4给出了@Bean的基本用法示,引用5展示了完整的测试流程。用户真正关心的显然是Bean注入后的状态管理问题。用户似乎是个有Spring使用经验的开发者,ta可能在开发过程中遇到了需要动态修改Bean属性的场景。考虑到之前讨论过@Bean的基础配置,现在用户的问题明显更深入了,涉及到Bean生命周期的管理。用户没有表现出急躁情绪,但这个问题确实比较关键,需要谨慎回答。技术上需要分几个层面来思考:首先明确SpringBean的默认作用域,然后区分修改属性的不同情况。Bean在容器启动后就不该直接修改,但原型模式每次获取都是新实。还要考虑用户可能的操作方式——是通过代码硬改还是通过Spring的扩展机制。特别要注意引用3提到的ConfigurationClassPostProcessor,这个底层处理器决定了@Bean方法的调用时机。容器初始化阶段就已经完成Bean的创建,所以后续直接修改确实会破坏IoC原则。不过Spring确实提供了替代方案,比如BeanPostProcessor或者刷新上下文。用户可能忽略了一个关键点:为什么官方不建议直接修改注入后的Bean?这涉及到Spring的容器管理哲学和线程安全问题。需要向用户解释清楚直接修改可能导致的后果,同时给出合规的解决方案路线图。啊,想到一个常见误区:很多开发者会尝试在@PostConstruct方法里修改属性,但这对Bean同样不安全。必须强调线程安全的实现方式,比如使用ThreadLocal或者完全避免状态保留。最后给出的代码示要能体现正确模式,特别是原型作用域的演示很重要。</think> 在Spring框架中,由`@Bean`注解创建的实在**注入完成后是否可以修改属性**,取决于Bean的作用域和设计方式,下面是详细解答: --- ### **一、默认情况:Bean注入后不建议直接修改** 1. **Bean特性** Spring默认创建的Bean(Singleton),即容器中仅存在一个实。 - ✅ **初始化后可以修改属性** 技术上可通过代码直接修改Bean的字段(如`setter`方法)。 ```java @Bean public MyService myService() { return new MyServiceImpl(); } // 注入后修改属性(存在风险) MyService service = context.getBean(MyService.class); service.setProperty("new value"); // 直接调用Setter修改 ``` - ⚠️ **潜在风险** 若多个线程同时修改Bean,会引发**线程安全问题**。因此官方**不推荐**这种做法[^1][^3]。 --- ### **二、安全修改属性的设计模式** #### **方案1:使用原型(Prototype)作用域** ```java @Bean @Scope("prototype") // 每次注入时创建新实 public MyService myService() { return new MyServiceImpl(); } ``` - **优势**:每次获取Bean都是新对象,修改属性不影响其他使用方[^3]。 - **局限**:频繁创建可能影响性能。 --- #### **方案2:利用Spring的扩展点** **通过`BeanPostProcessor`接口** 在Bean初始化前后插入自定义逻辑: ```java @Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { if (bean instanceof MyService) { ((MyService) bean).setProperty("pre-init value"); } return bean; } } ``` - ✅ **安全修改**:由Spring容器管理线程安全[^3]。 --- #### **方案3:刷新容器(极端场景)** ```java AnnotationConfigApplicationContext context = ... context.refresh(); // 重建所有Bean ``` - ⚠️ **代价高昂**:重建整个上下文,仅用于测试或特殊需求[^5]。 --- ### **三、重新定义Bean:需重建容器** - **Spring不支持运行时替换Bean** 若需彻底替换Bean定义,必须**重启应用**或**刷新上下文**(如`refresh()`),但会销毁所有[^3][^5]。 --- ### **四、最佳实践总结** | 场景 | 能否修改 | 推荐方案 | |---------------------|-------------|----------------------------| | Bean属性修改 | ✅ 技术上可行 | ❌ 避免直接修改(线程不安全) | | 原型Bean属性修改 | ✅ 安全 | 优先选择原型作用域 | | 初始化时注入动态值 | ✅ 安全 | 用`@PostConstruct`或`BeanPostProcessor` | | 完全替换Bean定义 | ❌ 不支持 | 重建Spring上下文 | > 📌 **关键原则**:Bean应当设计为**无状态(Stateless)** 或**只读(Immutable)**,避免直接修改属性。若需动态数据,推荐通过方法参数传递,或使用**ThreadLocal**封装[^1][^3]。 --- **相关问题** 1. 如何确保SpringBean的线程安全? 2. 在哪些场景下应该使用原型作用域(Prototype)? 3. `@PostConstruct`与`BeanPostProcessor`有何区别? 4. Spring中如何动态注册新的Bean定义?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值