What is circular dependency?
Let's talk about circular dependency first. Spring needs to inject B when initializing A, and A when initializing B. after spring starts, these two beans must be initialized
There are four scenarios for Spring's circular dependency
- singleton (prototype) of constructor
- Property (singleton, prototype)
"spring currently only supports attribute circular dependency of singleton type"
Loop dependency of constructor
@Component public class ConstructorA { private ConstructorB constructorB; @Autowired public ConstructorA(ConstructorB constructorB) { this.constructorB = constructorB; } }
@Component public class ConstructorB { private ConstructorA constructorA; @Autowired public ConstructorB(ConstructorA constructorA) { this.constructorA = constructorA; } }
@Configuration @ComponentScan("com.javashitang.dependency.constructor") public class ConstructorConfig { }
public class ConstructorMain { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConstructorConfig.class); System.out.println(context.getBean(ConstructorA.class)); System.out.println(context.getBean(ConstructorB.class)); } }
When running the main method of ConstructorMain, an exception will be reported in the first line, indicating that Spring cannot initialize all beans, that is, the above form of circular dependency cannot be solved by Spring.
"For the cyclic dependency of the constructor, you can use @ Lazy annotation in the constructor to delay loading. When injecting the dependency, first inject the proxy object, and then create the object when it is used for the first time to complete the injection"
@Autowired public ConstructorB(@Lazy ConstructorA constructorA) { this.constructorA = constructorA; }
Because we mainly focus on the circular dependency of attributes, the circular dependency of constructors will not be analyzed too much
Circular dependency of attributes
First, let's demonstrate what is the circular dependency of attributes
@Data @Component public class A { @Autowired private B b; }
@Data @Component public class B { @Autowired private A a; }
@Configuration @EnableAspectJAutoProxy @ComponentScan("com.javashitang.dependency") public class Config { }
public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); System.out.println(context.getBean(A.class).getB() == context.getBean(B.class)); System.out.println(context.getBean(B.class).getA() == context.getBean(A.class)); } }
The Spring container starts normally and the running result is true. It's not difficult to realize similar functions. I'll write a demo to demonstrate it
public class DependencyDemoV1 { private static final Map<String, Object> singletonObjects = new HashMap<>(256); @SneakyThrows public static <T> T getBean(Class<T> beanClass) { String beanName = beanClass.getSimpleName(); if (singletonObjects.containsKey(beanName)) { return (T) singletonObjects.get(beanName); } // Instantiate bean Object object = beanClass.getDeclaredConstructor().newInstance(); singletonObjects.put(beanName, object); // Start initializing the bean, that is, populating the properties Field[] fields = object.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); // Get the class of the field to be injected Class<?> fieldClass = field.getType(); field.set(object, getBean(fieldClass)); } return (T) object; } public static void main(String[] args) { // Fake scanned classes Class[] classes = {A.class, B.class}; for (Class aClass : classes) { getBean(aClass); } System.out.println(getBean(A.class).getB() == getBean(B.class)); System.out.println(getBean(B.class).getA() == getBean(A.class)); } }
"Before starting the following content, let's clarify two concepts"
Instantiation: call the constructor to create the object. Initialization: after calling the constructor to create the object, the attributes of the object are also assigned
It can be seen that the implementation of circular dependency is implemented with only one map, but this implementation has a small defect. The classes in singletonObjects may only complete instantiation and not initialization
In spring, the classes in singletonObjects have completed initialization, because we take singleton beans from singletonObjects, so it is impossible for us to get objects that have not been initialized.
So let's write the second implementation, "use singletonObjects to store the initialized object, and use earlySingletonObjects to temporarily store the instantiated object. After the object is initialized, put the object into singletonObjects and delete it from earlySingletonObjects."
public class DependencyDemoV2 { private static final Map<String, Object> singletonObjects = new HashMap<>(256); private static final Map<String, Object> earlySingletonObjects = new HashMap<>(256); @SneakyThrows public static <T> T getBean(Class<T> beanClass) { String beanName = beanClass.getSimpleName(); if (singletonObjects.containsKey(beanName)) { return (T) singletonObjects.get(beanName); } if (earlySingletonObjects.containsKey(beanName)) { return (T) earlySingletonObjects.get(beanName); } // Instantiate bean Object object = beanClass.getDeclaredConstructor().newInstance(); earlySingletonObjects.put(beanName, object); // Start initializing the bean, that is, populating the properties Field[] fields = object.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); // Get the class of the field to be injected Class<?> fieldClass = field.getType(); field.set(object, getBean(fieldClass)); } singletonObjects.put(beanName, object); earlySingletonObjects.remove(beanName); return (T) object; } public static void main(String[] args) { // Pretend to scan out the class Class[] classes = {A.class, B.class}; for (Class aClass : classes) { getBean(aClass); } System.out.println(getBean(A.class).getB() == getBean(B.class)); System.out.println(getBean(B.class).getA() == getBean(A.class)); } }
The current implementation is consistent with spring and uses only level 2 cache. Why does spring have a third cache? "The third cache is mainly related to proxy objects"
I'd better improve the above example and use the implementation of level 3 cache instead
public interface ObjectFactory<T> { T getObject(); }
public class DependencyDemoV3 { private static final Map<String, Object> singletonObjects = new HashMap<>(256); private static final Map<String, Object> earlySingletonObjects = new HashMap<>(256); private static final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(256); @SneakyThrows public static <T> T getBean(Class<T> beanClass) { String beanName = beanClass.getSimpleName(); if (singletonObjects.containsKey(beanName)) { return (T) singletonObjects.get(beanName); } if (earlySingletonObjects.containsKey(beanName)) { return (T) earlySingletonObjects.get(beanName); } ObjectFactory<?> singletonFactory = singletonFactories.get(beanName); if (singletonFactory != null) { return (T) singletonFactory.getObject(); } // Instantiate bean Object object = beanClass.getDeclaredConstructor().newInstance(); singletonFactories.put(beanName, () -> { Object proxy = createProxy(object); singletonFactories.remove(beanName); earlySingletonObjects.put(beanName, proxy); return proxy; }); // Start initializing the bean, that is, populating the properties Field[] fields = object.getClass().getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); // Get the class of the field to be injected Class<?> fieldClass = field.getType(); field.set(object, getBean(fieldClass)); } createProxy(object); singletonObjects.put(beanName, object); singletonFactories.remove(beanName); earlySingletonObjects.remove(beanName); return (T) object; } public static Object createProxy(Object object) { // Because this method may be executed twice, there should be a judgment here // If you have performed aop operation in advance, you can return directly. Just know the meaning and don't write it // If aop is required, the proxy object is returned; otherwise, the incoming object is returned return object; } public static void main(String[] args) { // Pretend to scan out the class Class[] classes = {A.class, B.class}; for (Class aClass : classes) { getBean(aClass); } System.out.println(getBean(A.class).getB() == getBean(B.class)); System.out.println(getBean(B.class).getA() == getBean(A.class)); } }
"Why wrap an ObjectFactory object?"
If the created Bean has a corresponding aop proxy, the corresponding proxy object should be injected when other objects are injected; "However, Spring cannot know whether this object has circular dependency in advance." under normal circumstances (without circular dependency), Spring creates the corresponding proxy only after the object is initialized. At this time, Spring has two choices:
- No matter whether there is circular dependency or not, the proxy object is directly created after instantiation, and the proxy object is put into the cache. When circular dependency occurs, other objects can directly get the proxy object and inject it (Only Level 2 cache is required, and singletonObjects and earlySingletonObjects can be used)
- "The proxy object is not created in advance. The proxy object is generated in advance only when the circular dependency is injected by other objects (only instantiation is completed at this time). In this way, when there is no circular dependency, the Bean still generates the proxy object after initialization" (Level 3 cache is required)
"So now you know the role of level 3 cache, mainly for the purpose that proxy objects can be generated after initialization, rather than in advance."
cache | explain |
---|---|
singletonObjects | The first level cache stores the beans after initialization |
earlySingletonObjects | The second level cache, which stores the beans that have been instantiated, may be proxied |
singletonFactories | Delay generation of proxy objects |
Source code analysis
When obtaining beans, first try to obtain them from the level 3 cache, which is similar to our Demo above
DefaultSingletonBeanRegistry#getSingleton
When it cannot be obtained from the cache, it will create AbstractAutowireCapableBeanFactory#doCreateBean (part of the code is deleted)
When circular dependency occurs, the proxy object will be obtained from the factory
When the aop agent is started, an implementation class of SmartInstantiationAwareBeanPostProcessor is AbstractAutoProxyCreator
AbstractAutoProxyCreator#getEarlyBeanReference
The getEarlyBeanReference method proxies in advance. In order to prevent proxiing again later, you need to use earlyProxyReferences to record that this Bean has been proxied and is no longer proxied
AbstractAutoProxyCreator#postProcessAfterInitialization
This method is the place for aop proxy. Because it is possible to proxy in advance, first judge whether to proxy in advance according to earlyProxyReferences. If you proxy in advance, you don't need to proxy
When the bean is initialized, it will be put into the first level cache and deleted from the second and third level cache
DefaultSingletonBeanRegistry#addSingleton
When circular dependency occurs, the overall execution process is as follows
Your "like / watching / sharing" is the biggest driving force for me to adhere to!
It's not easy to insist. Sell cute and roll for encouragement (ฅ > ω<* ฅ)