1, Foreword
Spring circular dependency is one of the interview sites. The interviewer can dig deep into the interviewer's knowledge of spring's Bean life cycle. Spring circular dependency is also one of the difficulties of spring. The logic is rather convoluted. You need to know the life cycle of spring beans like the back of your hand.
2, What is circular dependency?
Simply, A object depends on B object, and B object depends on A object.
@Component public class A { @Autowired B b; }
@Component public class B { @Autowired A a; }
3, How does Spring handle circular dependencies?
3.1 use words to describe the life cycle processes of two beans with specific circular dependencies
A life cycle 1. class file 2. adopt beanName Get the object from the cache. At the beginning, the singleton pool does not, and a It is not in the collection being created, so it will not go to the L2 cache or L3 cache to get objects and related codes[sign①] 3. Put objects into the collection being created(beforeSingletonCreation(beanName);) Related codes[sign②] 4. Instancing objects by reflection(createBeanInstance();) Related codes[sign③] 5. Add object factory to L2 cache related code[sign④] 6. Attribute population. Find the need here B Object, getting from the singleton pool B->null->And b Is not in the collection being created->instantiation B And complete B Life cycle related codes for[sign⑤] wait for b init... 7. Initialization related codes[sign⑥] 8. Get from L2 cache Bean Related codes[sign⑦] 9. Add to singleton pool related code[sign⑥] B life cycle 1. class file 2. adopt beanName Get the object from the cache. At the beginning, the singleton pool does not, and a It is not in the collection being created, so it will not be fetched from the L2 cache or L3 cache 3. Put objects into the collection being created(beforeSingletonCreation(beanName);) (singletonObject = singletonFactory.getObject();) 4. Instancing objects by reflection(createBeanInstance();) 5. Add object factory to L2 cache 6. Attribute population. Find the need here A Object, getting from the singleton pool A->null->here a In creating collection->Get objects from L3 cache(If a If there is a facet, the proxy object is returned),Put objects into L2 cache and delete L3 cache 7. initialization 8. Get from L2 cache Bean 9. Add to singleton pool
3.2 why do I need L3 cache to solve circular dependency?
Definition and function of L3 cache
- L1 cache: singletonObjects. Stores beans that have completed the Spring lifecycle. Note that objects and beans are completely two concepts in the Spring world. An object instantiated through reflection does not complete the Spring life cycle process (such as post processor). A Bean indicates that the whole Spring life cycle process has been completed.
- L2 cache: earlySingletonObjects. Stores objects that have been instantiated (created through reflection) but have not completed the Spring lifecycle process.
- L3 cache: singletonFactories. Store object factory. The goal is to generate AOP proxy objects through the object factory when appropriate.
How to solve
- By exposing yourself in advance (adding yourself to the L3 cache) to let the dependents inject, so as not to cause an endless loop, you can complete the rest of the process.
- The object factory is exposed to complete the AOP proxy. The object factory knows how to create an AOP proxy for an object, but it will not create it immediately. Instead, it will create an AOP proxy object at an appropriate time.
- One of the purposes of L2 cache is to ensure that an object has only one AOP proxy. When the getObject() method of the L3 cache is called, the returned object will be stored in the L2 cache. In this way, when the subsequent dependents call, they will first judge whether the L2 cache has a target object. If there is a target object, they will return directly.
3.3 important code snippets
Code ① - getSingleton
// DefaultSingletonBeanRegistry#getSingleton @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Get the bean from the L1 cache, that is, the singleton pool Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // Get object from L2 cache singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // Get object factory from L3 cache ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
Code ② - beforeSingletonCreation
// DefaultSingletonBeanRegistry#beforeSingletonCreation protected void beforeSingletonCreation(String beanName) { // 1.creationcheckeexclusions: judge whether the annotation excludeFilters=xx in the [excluded] collection object exists in the current object // 2.singletonsCurrentlyInCreation: add the current object to the [creating singleton Bean] collection if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } }
Code ③ - createBeanInstance
// AbstractAutowireCapableBeanFactory#createBeanInstance protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { // In case of automatic assembly, various candidate construction methods are inferred Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { // Use the inferred practice construction method to instantiate the object. If constructor injection is used, it is returned directly here return autowireConstructor(beanName, mbd, ctors, args); } ctors = mbd.getPreferredConstructors(); if (ctors != null) { // Use the inferred candidate construction method to instantiate the object return autowireConstructor(beanName, mbd, ctors, null); } // No special handling: simply use no-arg constructor. // If no suitable construction method is inferred (or no special construction method is provided), the default construction method is used return instantiateBean(beanName, mbd); }
Code ④ - addSingletonFactory
// DefaultSingletonBeanRegistry#addSingletonFactory 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); this.registeredSingletons.add(beanName); } } }
Code ⑤ - populateBean
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } // There are three post processors: // 1,ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor // 2,AutowiredAnnotationBeanPostProcessor // 3,CommonAnnotationBeanPostProcessor // AutowiredAnnotationBeanPostProcessor: resolve @Autowire attribute injection for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { // Automatic attribute injection mainly uses the AutowiredAnnotationBeanPostProcessor post processor PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { if (filteredPds == null) { filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); } pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); if (pvsToUse == null) { return; } } pvs = pvsToUse; } } }
Code ⑥ - initializeBean
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged((PrivilegedAction<Object>) () -> { invokeAwareMethods(beanName, bean); return null; }, getAccessControlContext()); } else { // 4-1. Execute Aware (BeanNameAware, BeanClassLoaderAware, BeanFactoryAware) invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { // 4.2 before initialization // △ execute Bean post processor (lifeCycle Callback) // Rewrite this method [postProcessBeforeInitialization] and it will be called here // lifeCycle callback init by annotation wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { // 4.3 initialization [InitializingBean] // lifeCycle callback init by interface. invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } if (mbd == null || !mbd.isSynthetic()) { // 4.4 after initialization // Complete AOP and generate dynamic agent; Event release; monitoring wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
Code ⑦ - doCreateBean
// AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) if (earlySingletonExposure) { // When resolving circular dependency, obtain beanName from L2 cache Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } ... } } ...
Code ⑧ - getSingleton
// DefaultSingletonBeanRegistry#getSingleton public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { // Put the current object into the singletonscurrentlyincreation beforeSingletonCreation(beanName); // The getObject method will call the createBean method of AbstractAutowireCapableBeanFactory singletonObject = singletonFactory.getObject(); //Add to singleton pool addSingleton(beanName, singletonObject); } }
4, Different injection methods and cyclic dependency
Dependency injection method | Can circular dependency be resolved |
---|---|
Are injected by setter method | can |
Constructor injection | No |
The method of injecting B in A is setter method, and the method of injecting A in B is constructor | can |
The method of injecting B in A is constructor, and the method of injecting A in B is setter method | No |
Note: A is created first.
Summary:
- The primary object (the class with cyclic dependency and loaded first, abbreviated as A) cannot inject the dependent Bean object (abbreviated as B) through the constructor method, but B is not restricted and can be injected in any way (setter, @Autowire, constructor).
- The reason why A object in circular dependency cannot use constructor injection is that when type inference is made in the abstractautowirecapablebeanfactory \createbeaninstance method, if the inference is to construct method instance B, it will directly call ` `abstractautowirecapablebeanfactory \autowireconstructor, which will create an object by calling abstractautowirecapablebeanfactory \docreatebean, However, because the A object does not expose the factory in advance (no chance is given), it can only be created step by step. However, when you go to the beforeSingletonCreation method of the code defaultsingletonbeanregistry\getsingleton, because A is creating A collection at this time, if you force it to add, it will return false`, and an exception will be thrown. Relevant code ③
What if we solve circular dependency?
- Delay construction
- Using setter injection
4, Summary
- The Spring team suggests that we use the constructor based method to manage our dependencies and use assertions for mandatory dependencies. setter injection is recommended here.
- How does Spring solve circular dependency?
- Spring solves circular dependency through three-level cache. Other L1 caches are singleton pools, L2 caches are early exposure objects, and L3 caches are early exposure object factories. When circular references occur to classes a and B, after instantiation of class A, the factory object will be exposed in advance (that is, it will be added to the L3 cache). If a is the initial AOP proxy, the factory object will return the proxied object. If it is not proxied, the object itself will be returned. When a performs attribute injection, it goes through the previous instantiation step. At this time, it is B's turn to inject attributes and call getBean(a) to obtain the a object. Since a processing is creating a collection, it also sends a circular dependency at this time, so you can obtain the object factory from the L3 cache (if a is managed by AOP, then the returned object is a proxy object), and put the object into the L2 cache, so as to ensure that a only passes through the AOP proxy once. Next, B completes the spring lifecycle process and puts it into the singleton pool. After B is created, B will be injected into a, and a will complete the spring life cycle process. At this point, the circular dependency ends.