SpringBoot源码(十四):spring三级缓存源码,解决循环依赖

介绍

在写springboot项目时,我们对于bean的注入都使用注解的方式进行注入。可以根据field注入,set方法注入还有构造器方法注入。但是我们都写过类似下面的代码:

@Service
public class Bean1 {
    @Autowired
    private Bean2 bean2;
}

@Service
public class Bean2 {

    @Autowired
    private Bean1 bean1;
}

即Bean1和Bean2互相引用,但是也没有出异常。

这是怎么回事呢?

其实主要原因时spring替我们处理了,处理的方式就是我们要说的三级缓存。但是为什么三级缓存无法解决构造器循环依赖呢?接下来通过源码来一一解答。

源码

在看源码之前先来介绍下什么是三级缓存,其实上一篇介绍spring的获取bean的方法时候已经说过了,这里再提一下

1. singletonObjects为一级缓存,我们实例化的bean都在这个map里,侠义的说singletonObjects才是我们真正的spring容器,存放bean的地方。

2. earlySingletonObjects为二级缓存,是存放未完成的bean的缓存,如果有代理的话,存放的是代理对象。

3. singletonFactories为三级缓存,存放的是一个ObjectFactory,数据通过getObject方法获得。

说完三级缓存的大概作用,来介绍下三级缓存的使用吧。其中有些需要涉及到spring获取bean的方法,不了解的话可以看下上一篇的大致流程。

首先从获取bean开始看吧

获取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;

		// 从三级缓存中获取
		Object sharedInstance = getSingleton(beanName);
        
        /**
        * 省略代码
        */
	}

	@Override
	@Nullable
	public Object getSingleton(String beanName) {
		return getSingleton(beanName, true);
	}

	@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的时候,最开始我们会先从三级缓存中获取,如果获取不到,才会去创建bean。

 

创建bean时,第三级缓存应用

	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
			throws BeanCreationException {

		// Instantiate the bean.
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		final Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}

        /** 
        * 省略代码
        */
                
        // 第三级缓存
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isDebugEnabled()) {
				logger.debug("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}
        /** 
        * 省略代码
        */
		return exposedObject;
	}

首先省略了一些暂时不需要的代码,看下实例化bean之后加入三级缓存部分。

		if (earlySingletonExposure) {
			if (logger.isDebugEnabled()) {
				logger.debug("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
            // 加入到三级缓存中
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
                // 加入到三级缓存中
				this.singletonFactories.put(beanName, singletonFactory);
                // 删除二级缓存
				this.earlySingletonObjects.remove(beanName);
                // 加入注册bean的set中
				this.registeredSingletons.add(beanName);
			}
		}
	}

这部分代码就是把创建的bean封装成ObjectFactory对象,加入到三级缓存中,并且删除二级缓存。现在三级缓存就有了,只不过是封装成了ObjectFactory对象。

接下来就要查找那些需要被注入的bean了。比如上面举例的Bean1,在实例化Bean1之后,还需要去把Bean2注入到Bean1中。

那么可以想象下接下来的流程,那就是去找到Bean2,并且实例化Bean2,并且把Bean2注入到Bean1,并且作为Bean1的field。

既然大致的流程已经知道了,看看spring中的实现吧。实现就在populateBean中。

populateBean填充bean属性

	protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
        // 封装bean的对象为空,则进行一些处理和验证
		if (bw == null) {
			if (mbd.hasPropertyValues()) {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
			}
			else {
				// Skip property population phase for null instance.
				return;
			}
		}

		boolean continueWithPropertyPopulation = true;

        // 看代码逻辑应该是可以实现InstantiationAwareBeanPostProcessor
        // 自定义去实现注入属性的逻辑,这样就不用再去走spring自己的方式去查找需要注入的bean
        // 和注入方式了
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
						continueWithPropertyPopulation = false;
						break;
					}
				}
			}
		}

		if (!continueWithPropertyPopulation) {
			return;
		}

		PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
        // 根据名称注入还是根据类型注入
		if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
				mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);

			// 根据名称注入
			if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
				autowireByName(beanName, mbd, bw, newPvs);
			}

			// 根据类型注入
			if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
				autowireByType(beanName, mbd, bw, newPvs);
			}

			pvs = newPvs;
		}

		boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
		boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);

		if (hasInstAwareBpps || needsDepCheck) {
			if (pvs == null) {
				pvs = mbd.getPropertyValues();
			}
            // java自省,找到那些自省规范的属性,然后排除掉spring忽略的
			PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
			if (hasInstAwareBpps) {
				for (BeanPostProcessor bp : getBeanPostProcessors()) {
					if (bp instanceof InstantiationAwareBeanPostProcessor) {
						InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                        // 根据注解注入
						pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
						if (pvs == null) {
							return;
						}
					}
				}
			}
			if (needsDepCheck) {
				checkDependencies(beanName, mbd, filteredPds, pvs);
			}
		}

		if (pvs != null) {
            // 将属性应用到bean上
			applyPropertyValues(beanName, mbd, bw, pvs);
		}
	}

populateBean是查找bean中的属性,找到需要注入的属性。然后设置到bean上。来梳理下populateBean的流程。

1. 检查包装bean的包装对象是不是空,是空话判断是不是有需要注入的属性,如果有则抛出异常,没有则返回。

2. 接下来查找实现InstantiationAwareBeanPostProcessor的BeanPostProcessor,调用实例化之后的后置方法。如果返回false,则不会再走spring自己的属性配置。这个应该是留给需要自定义装配的。在springboot源码中,我暂时没有看到返回false的BeanPostProcessor。按照我自己的想法,如果自己去实现属性的装配,去实现InstantiationAwareBeanPostProcessor就可以了。

3. 根据配置的模式来判断是根据类型注入还是根据名称装配。其实我们一般使用springBoot的时候,这里默认是手动装配。

4. java自省,获取那些符合自省的属性

5. InstantiationAwareBeanPostProcessor的BeanPostProcessor会根据注解查找需要装配的bean

6. 如果属性配置的值不为空,将配置的属性应用到bean上

populateBean大体的逻辑就是这样,对应上上面Bean1在来说下,也就是要到第5步时候,根据去BeanPostProcessor查找需要装配的Bean了。对于@Autowired注解,对应的是AutowiredAnnotationBeanPostProcessor。

那就来看这个processor是如何处理的。

AutowiredAnnotationBeanPostProcessor处理Field装配

	@Override
	public PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
        
        // 查找带有@Autowired注解的Field或者method,封装在InjectionMetadata中
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
            // 数据注入
			metadata.inject(bean, beanName, pvs);
		}
		catch (BeanCreationException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
		}
		return pvs;
	}


	public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate =
				(checkedElements != null ? checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			boolean debug = logger.isDebugEnabled();
			for (InjectedElement element : elementsToIterate) {
				if (debug) {
					logger.debug("Processing injected element of bean '" + beanName + "': " + element);
				}
                // 根据是field还是method调用不同的方法
				element.inject(target, beanName, pvs);
			}
		}
	}

AutowiredAnnotationBeanPostProcessor首先会去查找带有@Autowired注解的字段或者方法,同样父类也会去找。找到之后封装在InjectionMetadata中,并且对于AutowiredAnnotationBeanPostProcessor分为FieldElement和MethodElement。然后在进行调用。

具体查找需要装配的方法在findAutowiringMetadata中。其实这边在调用时候也是从缓存中拿,真正查找是在doCreateBean的applyMergedBeanDefinitionPostProcessors方法中。

接着向下看,根据FieldElement来进行调用

		@Override
		protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
			Field field = (Field) this.member;
			Object value;
			if (this.cached) {
                // 如果已经缓存过了,则通过缓存快速获取
				value = resolvedCachedArgument(beanName, this.cachedFieldValue);
			}
			else {
                // 数据封装成DependencyDescriptor
				DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
                // 设置class
				desc.setContainingClass(bean.getClass());
				Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
				Assert.state(beanFactory != null, "No BeanFactory available");
                // 类型转换器
				TypeConverter typeConverter = beanFactory.getTypeConverter();
				try {
                    // 获取依赖的Bean
					value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
				}
				catch (BeansException ex) {
					throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
				}
				synchronized (this) {
                    // 还没缓存过
					if (!this.cached) {
						if (value != null || this.required) {
							this.cachedFieldValue = desc;
                            // 加入到依赖的map中
							registerDependentBeans(beanName, autowiredBeanNames);
							if (autowiredBeanNames.size() == 1) {
								String autowiredBeanName = autowiredBeanNames.iterator().next();
								if (beanFactory.containsBean(autowiredBeanName)) {
                                    // 装配的bean和field字段类型匹配,则缓存信息
									if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
										this.cachedFieldValue = new ShortcutDependencyDescriptor(
												desc, autowiredBeanName, field.getType());
									}
								}
							}
						}
						else {
							this.cachedFieldValue = null;
						}
						this.cached = true;
					}
				}
			}
			if (value != null) {
				ReflectionUtils.makeAccessible(field);
                // 需要装配的bean设置到当前bean字段上
				field.set(bean, value);
			}
		}
	}

这段逻辑其实很简单,就是找到需要装配的bean,缓存之后,需要装配的bean设置到当前bean字段上。

对比下Bean1的例子,就是获得到了Bean2之后,把Bean2设置到Bean1的field上。

接着看如何获取需要装配的Bean的方法吧

	@Override
	@Nullable
	public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
        // 根据类型来处理
		if (Optional.class == descriptor.getDependencyType()) {
			return createOptionalDependency(descriptor, requestingBeanName);
		}
		else if (ObjectFactory.class == descriptor.getDependencyType() ||
				ObjectProvider.class == descriptor.getDependencyType()) {
			return new DependencyObjectProvider(descriptor, requestingBeanName);
		}
		else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
			return new Jsr330ProviderFactory().createDependencyProvider(descriptor, requestingBeanName);
		}
		else {
            // 懒加载配置
			Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
					descriptor, requestingBeanName);
			if (result == null) {
                // 默认处理依赖的bean
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
			}
			return result;
		}
	}

	@Nullable
	public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
		try {
            // 如果是之前缓存的,直接通过缓存去获取bean,省略下面获取的过程
			Object shortcut = descriptor.resolveShortcut(this);
			if (shortcut != null) {
				return shortcut;
			}
            
            // 依赖bean的类型
			Class<?> type = descriptor.getDependencyType();
            // 支持@Value注解
			Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
			if (value != null) {
				if (value instanceof String) {
					String strVal = resolveEmbeddedValue((String) value);
					BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null);
					value = evaluateBeanDefinitionString(strVal, bd);
				}
				TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
				return (descriptor.getField() != null ?
						converter.convertIfNecessary(value, type, descriptor.getField()) :
						converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
			}
            
            // 批量获取bean
			Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
			if (multipleBeans != null) {
				return multipleBeans;
			}
            
            // 根据类型获取匹配的bean或者class
			Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
			if (matchingBeans.isEmpty()) {
				if (isRequired(descriptor)) {
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
				return null;
			}

			String autowiredBeanName;
			Object instanceCandidate;
            
            // 如果根据类型匹配有多个,查找符合的那个
			if (matchingBeans.size() > 1) {
				autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
				if (autowiredBeanName == null) {
					if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
						return descriptor.resolveNotUnique(type, matchingBeans);
					}
					else {
						return null;
					}
				}
				instanceCandidate = matchingBeans.get(autowiredBeanName);
			}
			else {
				
				Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
				autowiredBeanName = entry.getKey();
				instanceCandidate = entry.getValue();
			}

			if (autowiredBeanNames != null) {
				autowiredBeanNames.add(autowiredBeanName);
			}
            // 如果获取到的对象是class,则去获取bean
			if (instanceCandidate instanceof Class) {
				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
			}
			Object result = instanceCandidate;
			if (result instanceof NullBean) {
				if (isRequired(descriptor)) {
					raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
				}
				result = null;
			}
			if (!ClassUtils.isAssignableValue(type, result)) {
				throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
			}
			return result;
		}
		finally {
			ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
		}
	}



	public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
			throws BeansException {
        // 熟悉的代码,获取bean
		return beanFactory.getBean(beanName);
	}

由于获取bean的流程特别长,这边说下主流程,因为主要讲的是三级缓存,所以细致的就不说了,后面说根据类型装配的时候,会一点一点的细讲这段代码。

先梳理下这边的doResolveDependency的流程

1. 对于已经缓存过的,也就是上面流程说到的封装成ShortcutDependencyDescriptor的,直接通过ShortcutDependencyDescriptor获取。

2. 获取依赖bean的类型

3. 根据bean的类型,看看是不是那种批量的类型,比如Array,Collection,Map等。如果是就批量获取bean。(这里也就是spring为什么能批量装配的原因)

4. 根据类型去获取bean对象或者class

5. 如果是class,则根据calss获取bean

6. 如何根据class获取bean呢?在resolveCandidate中我们又看到了熟悉的方法,beanFactory.getBean。 这样就又走到了getBean的方法了。

7. 对应例子bean1和bean2说下。

8. 此时去获取Bean2,但是由于Bean2不存在,所以去创建Bean2

9. 创建bean2之后,走到populateBean之后又发现Bean2依赖Bean1。像上面流程一样,又会去getBean获取Bean1。但是Bean1此时已经在三级缓存中了,也就是开始看的,通过getSingleton方法在三级缓存获取Bean1。这时就拿到了还未完全创建好的Bean1供Bean2使用

10. 等bean2实例化完成之后,再回到Bean1的创建上了。把创建好的Bean2设置到Bean1的字段上。这样就完成了对Bean1的创建。整个循环依赖的创建也就完成了

详细看下图吧。

 

 

构造器循环依赖

先看一段构造器循环依赖的代码

@Service
public class Bean1 {

    private Bean2 bean2;

    @Autowired
    public Bean1(Bean2 bean2) {
        this.bean2 = bean2;
    }
}


@Service
public class Bean2 {

    @Autowired
    private Bean1 bean1;
}

那为什么无法解决构造器依赖呢 ?其实通过流程以及可以发现了,spring创建的bean放入三级缓存是在创建实例之后,但是构造器依赖时,实例还有没有创建,所以无法解决构造器依赖。具体代码暂时先不分析了,先给一下构造器依赖的流程图吧。大体上差不多。

这边大概梳理下流程,因为构造器注入源码也非常的多,后面再细细讲解吧。

1. 同样先创建Bean1,然后创建的方法

2. 在调用createBean之前会先把bean1放到singletonsCurrentlyInCreation正在创建bean的set中。

3. 调用创建bean的方法,先去查找Bean1有没有那种有参数的构造函数,如果有,找到确定的构造函数

4. 根据构造函数去实例化bean,也就是此时找到了构造函数Bean2,去实例化Bean2。

5. 在实例化Bean2的时候发现依赖Bean1,再去实例化Bean1。

6. 但是Bean1没有在三级缓存中,因为三级缓存必须等Bean1创建之后才会加入。所以此时又去新创建Bean1,但是Bean1此时已经在创建singletonsCurrentlyInCreation的set中,所以抛出异常。

这就是为什么spring无法解决构造器循环依赖的大体流程了。和根据filed,和set方法注入整体思路差不多。源码以后再进行分析。

总结

本章大体说了下spring对于循环依赖的处理方式,也就是通过三级缓存。还有为什么无法解决构造器依赖的问题。由于篇幅太长,构造器注入的源码就没分析,以后会再去分析。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
源码深度解析是一种深入研究源代码的方法,通过仔细阅读和理解源代码中的细节和逻辑,以获得对代码的深刻理解和洞察。这样的分析可以帮助开发者更好地理解代码的实现方式,从而更好地理解并使用该代码库。 关于spring如何解决循环依赖的问题,我们可以从源码的角度来分析。Spring采用了三级缓存解决循环依赖的问题。 第一级缓存是singletonFactories缓存,用于存储正在创建的Bean的工厂对象。当容器正在创建一个Bean时,会将这个Bean的工厂对象存储在singletonFactories缓存中。 第二级缓存是earlySingletonObjects缓存,用于存储已经完成了属性填充但尚未初始化完成的Bean。当容器创建一个Bean时,会将正在创建的Bean存储在earlySingletonObjects缓存中。 第三级缓存是singletonObjects缓存,用于存储已经完成初始化的Bean。当一个Bean初始化完成后,会将其存储在singletonObjects缓存中。 Spring在创建Bean的过程中,会先查找一级缓存,如果找到了对应的工厂对象,则直接返回该对象,避免了创建过程中的循环依赖。如果一级缓存中没有找到对应的工厂对象,则通过递归的方式创建依赖的Bean。 在创建Bean的递归过程中,如果发现正在创建的Bean已经在二级缓存中,说明发生了循环依赖。此时,Spring会从二级缓存中获取正在创建的Bean的代理对象,以解决循环依赖。 当一个Bean创建完成后,会将其放入三级缓存中,并从一级缓存和二级缓存中移除。 总结来说,Spring通过三级缓存的方式解决循环依赖的问题,保证了Bean的创建过程中不会陷入无限递归的循环。这种机制的实现使得Spring解决循环依赖问题上具有较好的性能和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值