What is a circular dependency?
When an object is nested with multiple layers of objects, why doesn’t it cause an infinite loop? Or by how special treatment?
Code Implementation
package com.hong.model; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.Serializable; /** * @ClassName OrderInfo * @Description order bean * @Author csh * @Date 2023/2/17 17:19 */ @Component public class OrderInfo{ //order number private String orderNo; //Payment status private String payStatus; //User Info private User userInfo; public String getOrderNo() { return orderNo; } public void setOrderNo(String orderNo) { this. orderNo = orderNo; } public String getPayStatus() { return payStatus; } public void setPayStatus(String payStatus) { this.payStatus = payStatus; } public User getUserInfo() { return userInfo; } public void setUserInfo(User userInfo) { this. userInfo = userInfo; } public OrderInfo() { System.out.println("The order is being initialized..."); this.orderNo = "123456"; } @Override public String toString() { return "OrderInfo{" + "orderNo='" + orderNo + ''' + ", payStatus='" + payStatus + ''' + ", userInfo=" + userInfo + '}'; } }
package com.hong.model; import org.springframework.stereotype.Component; import java.io.Serializable; /** * @ClassName User * @Description user * @Author csh * @Date 2023/1/13 14:27 */ @Component public class User{ private String userName; private int age; private String nickName; private OrderInfo orderInfo; public String getUserName() { return userName; } public void setUserName(String userName) { this. userName = userName; } public int getAge() { return age; } public void setAge(int age) { this. age = age; } public String getNickName() { return nickName; } public void setNickName(String nickName) { this.nickName = nickName; } public User() { System.out.println("User is initializing..."); this.userName = "hong"; } public OrderInfo getOrderInfo() { return orderInfo; } public void setOrderInfo(OrderInfo orderInfo) { this. orderInfo = orderInfo; } @Override public String toString() { return "User{" + "userName='" + userName + ''' + ", age=" + age + ", nickName='" + nickName + ''' + ", orderInfo=" + orderInfo + '}'; } }
public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext((TestMain.class)); OrderInfo orderInfo = applicationContext. getBean(OrderInfo. class); System.out.println(orderInfo.toString()); User user = applicationContext. getBean(User. class); System.out.println(user.toString()); }
What circular dependencies does spring only resolve?
Location |
whether to solve |
|
property setter injection |
yes |
|
property auto-injection |
yes |
For example: @Autowired |
Constructor auto-injection |
yes |
|
Constructor circularly depends on another object |
no |
A constructor depends on another constructor |
spring three-level cache
cache name |
Description |
Remarks |
singletonObjects |
The cache stores the Bean instances that have been created, that is, the Bean in the singleton mode. After the Bean is created, put the Bean instance into the cache. |
level one |
earlySingletonObjects |
The cache stores the Bean instance being created, that is, the Bean that is in the instantiation and dependency injection phase when the Bean is being created. When the Bean is created, the Bean instance is put into the cache to solve the circular dependency problem. |
Secondary |
singletonFactories |
The cache stores the Bean instance factory being created, that is, the factory object used to create the Bean instance. When the bean is created, if the bean needs to be proxied, the proxy factory object of the bean will be put into the cache. |
Level three |
The role of the third-level cache is to ensure thread safety and circular dependency resolution during the Bean creation process. Among them, the Bean instances in the singletonObjects cache are thread-safe, but the Bean instances in the earlySingletonObjects and singletonFactories caches are not thread-safe, and thread safety needs to be guaranteed through a synchronization mechanism. At the same time, earlySingletonObjects and singletonFactories caching is also the key to solving the circular dependency problem. When there is a circular dependency between two beans, the infinite loop problem caused by the circular dependency can be avoided by obtaining the Bean instance from the cache.
Source code learning
Initialize beans
When spring enters the finishBeanFactoryInitialization in refresh() during initialization, the remaining non-lazy-loaded beans will be instantiated, including the properties that the object depends on. The specific follow-up is as follows.
in org.springframework.context.support.AbstractApplicationContext#refresh
... // Instantiate all remaining (non lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); ...
Then enter the initialization of the Bean factory of the context, and initialize all remaining singleton beans in which preInstantiateSingletons will instantiate properties
// Instantiate all remaining (non lazy initialization) singletons beanFactory. preInstantiateSingletons();
@Override public void preInstantiateSingletons() throws BeansException { ... // get beans getBean(beanName); ... }
Called the doGetBean method to get the bean instance
@Override public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); }
doGetBean calls getSingleton to get it from the cache, but the first time it comes in here is actually empty.
Then it will judge whether mbd.isSingleton() is a singleton, and if it calls getSingleton.. to create a bean.
/** * Returns an instance of the specified Bean, which may be shared or independent. * @param name the name of the bean to retrieve * @param requiredType type * @param args arguments to use when creating a bean instance using explicit arguments * (only applied when creating a new instance as opposed to retrieving an existing one) * @param typeCheckOnly whether the instance is obtained for a type check, * not for actual use * @return Bean instance * @throws BeansException if the bean could not be created */ @SuppressWarnings("unchecked") protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { //Get the bean name final String beanName = transformedBeanName(name); Object bean; // Get an instance from the cache (core method ****) Object sharedInstance = getSingleton(beanName); ... // Determine whether it is a singleton mode if (mbd. isSingleton()) { // get instance sharedInstance = getSingleton(beanName, () -> { try { //Create bean and return 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); } // determine whether it is a prototype else if (mbd. isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } ... return (T) bean; }
doCreateBean mainly does several things here,
-
Create beans: createBeanInstance
-
Put it in the cache: addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
-
Populate property: populateBean(beanName, mbd, instanceWrapper); solves the problem of property dependence here.
-
Finally return: exposedObject
/** * Preprocessing creates the specified bean. Here is the implementation of the initialization properties in the third-level cache. * @param beanName the name of the bean * @param mbd the merged bean definition for the bean * @param args explicit arguments to use for constructor or factory method invocation * @return a new instance of the bean * @throws BeanCreationException if the bean could not be created * @see #instantiateBean * @see #instantiateUsingFactoryMethod * @see #autowireConstructor */ protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // instantiate the bean BeanWrapper instanceWrapper = null; //Single case judgment if (mbd. isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { //Create a singleton bean and return it with BeanWrapper. instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper. getWrappedInstance(); Class<?> beanType = instanceWrapper. getWrappedClass(); if (beanType != NullBean. class) { mbd.resolvedTargetType = beanType; } // Allow post-processors to modify the merged bean definition. synchronized(mbd. postProcessingLock) { if (!mbd. postProcessed) { try { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd. getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = (mbd. isSingleton() & amp; & amp; this. allowCircularReferences & amp; & amp; isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger. isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "'to allow for resolving potential circular references"); } //Add to the singletonFactories factory cache addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { //Fill dependencies Here is the implementation of parsing circular dependencies populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { if (ex instanceof BeanCreationException & amp; & amp; beanName.equals(((BeanCreationException) ex).getBeanName())) { throw (BeanCreationException) ex; } else { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping & amp; & amp; hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans. length); for (String dependentBean : dependentBeans) { if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { actualDependentBeans.add(dependentBean); } } if (!actualDependentBeans. isEmpty()) { throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils. collectionToCommaDelimitedString(actualDependentBeans) + "] in its raw version as part of a circular reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " + "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example."); } } } } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
/** * fill attribute * * @param beanName the name of the bean * @param mbd the bean definition for the bean * @param bw the BeanWrapper with bean instance */ @SuppressWarnings("deprecation") // for postProcessPropertyValues protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { ... PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd. getPropertyValues(); } for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; // get attribute injection PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } pvs = pvsToUse; } } } if (needsDepCheck) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } checkDependencies(beanName, mbd, filteredPds, pvs); } if (pvs != null) { applyPropertyValues(beanName, mbd, bw, pvs); } }
@Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { 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; }
... if (value != null || this.required) { this.cachedFieldValue = desc; registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames. size() == 1) { String autowiredBeanName = autowiredBeanNames. iterator(). next(); //The following logic is to get the injection corresponding to the property. beanFactory.isTypeMatch internally calls getSingleton if (beanFactory. containsBean(autowiredBeanName) & amp; & amp; beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { this.cachedFieldValue = new ShortcutDependencyDescriptor( desc, autowiredBeanName, field. getType()); } } } ...
by checking whether a bean with the given name matches the specified type.
protected boolean isTypeMatch(String name, ResolvableType typeToMatch, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException { String beanName = transformedBeanName(name); boolean isFactoryDereference = BeanFactoryUtils.isFactoryDereference(name); // get cached value Object beanInstance = getSingleton(beanName, false); .... }
At this point, spring will recursively call getSingleton according to the properties of the object, and create it if it does not exist. The whole process is called cyclically.
Get bean
Then the obtained bean has actually been instantiated.
The method to get the bean
@Override public <T> T getBean(Class<T> requiredType) throws BeansException { assertBeanFactoryActive(); return getBeanFactory().getBean(requiredType); }
Core code: used to parse Bean, ResolvableType.forRawClass(requiredType) is used to obtain the ResolvableType object of requiredType, args is the parameter of the constructor, false means that the circular dependency of Bean is not allowed
@Override public <T> T getBean(Class<T> requiredType, @Nullable Object... args) throws BeansException { Assert.notNull(requiredType, "Required type must not be null"); /** Core code: used to parse Bean, ResolvableType.forRawClass(requiredType) is used to obtain the ResolvableType object of requiredType, args is the parameter of the constructor, false means that the circular dependency of Bean is not allowed **/ Object resolved = resolveBean(ResolvableType. forRawClass(requiredType), args, false); if (resolved == null) { throw new NoSuchBeanDefinitionException(requiredType); } return (T) resolved; }
... @Override public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException { String beanName = transformedBeanName(name); // Obtain through L3 cache Object beanInstance = getSingleton(beanName, false); if (beanInstance != null) { return (beanInstance instanceof FactoryBean); } // No singleton instance found -> check bean definition. if (!containsBeanDefinition(beanName) & amp; & amp; getParentBeanFactory() instanceof ConfigurableBeanFactory) { // No bean definition found in this factory -> delegate to parent. return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name); } return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName)); } ...
/** The core acquisition method of the third-level cache first obtains from the first-level cache (singletonObjects), * if empty and not being created * The synchronous lock method is obtained from the second-level cache (earlySingletonObjects) * The second-level cache acquisition is empty and the provided loading flag is true * Acquired through a singleton factory (singletonFactories) and if it is not empty, it will be initialized and placed in the primary and secondary cache * * * return fetch result */ @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this. singletonObjects. get(beanName); if (singletonObject == null & amp; & amp; isSingletonCurrentlyInCreation(beanName)) { synchronized (this. singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null & amp; & amp; allowEarlyReference) { ObjectFactory<?> singletonFactory = this. singletonFactories. get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory. getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this. singletonFactories. remove(beanName); } } } } return singletonObject; }
Why is there a third-level cache?
Initializing attributes from the source code relies on the second-level cache (earlySingletonObjects) for implementation, then the third-level cache (singletonFactories) mainly solves the problem that the proxy in AOP needs to store the proxy object in the cache in advance, and the proxied object only needs to save the proxy factory It is placed in the third-level cache without instantiation, so here the third-level cache stores the proxy factory, not the instantiated object, and the object is only instantiated when it needs to be called (taken from the first and second levels) ), so this design is more reasonable, the first level is directly the instance, the second level cache solves the object attribute injection at this time, the object is not complete, and the third level stores the agent’s guidance, not the specific implementation. In fact, the final point here is through this proxy Signature callback acquisition results, clear division of labor at each level, is also relatively easy to understand.
Personal understanding: the factory’s g finished products (level 1 cache), semi-finished products (level 2 cache), orders (raw materials) (level 3 cache).
Last
According to the follow-up source code, it can be clearly understood that the circular dependency of spring is to instantiate the non-lazy-loaded bean in the refresh method when starting and initializing. Of course, if there is a circular dependency injection attribute, the attribute is initialized. Through two Level can solve the problem of dependence, three-level cache (singletonFactories) is used to solve the index location in advance such as AOP proxy. The code is relatively cumbersome, and it is recommended to follow up the debug in depth.
Reference:
https://www.bilibili.com/video/BV18a411P7cq/
https://blog.csdn.net/weixin_43716742/article/details/124448697
https://www.zhihu.com/question/445446018
https://zhuanlan.zhihu.com/p/496273636
https://www.cnblogs.com/daimzh/p/13256413.html