introduce
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 two scenarios for Spring's circular dependency
- Loop dependency of constructor
- Circular dependency of attributes
For the loop dependency of the constructor, you can use the @ Lazy annotation in the constructor to delay loading. When injecting dependencies, the proxy object is injected first, and then the object is created to complete the injection when it is used for the first time
The circular dependency of attributes is mainly solved through three map s
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 { } 1234 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, Spring cannot solve the above circular dependency.
We can solve this problem by adding @ Lazy annotation to the parameters of constructor a or constructor B
@Autowired public ConstructorB(@Lazy ConstructorA constructorA) { this.constructorA = constructorA; }
Because we mainly focus on the cyclic dependency of attributes, the cyclic dependency of constructors will not be analyzed too much
Circular dependency of attributes
Let's first demonstrate what is a circular dependency of attributes
@Component public class FieldA { @Autowired private FieldB fieldB; } @Component public class FieldB { @Autowired private FieldA fieldA; } @Configuration @ComponentScan("com.javashitang.dependency.field") public class FieldConfig { } 1234 public class FieldMain { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FieldConfig.class); // com.javashitang.dependency.field.FieldA@3aa9e816 System.out.println(context.getBean(FieldA.class)); // com.javashitang.dependency.field.FieldB@17d99928 System.out.println(context.getBean(FieldB.class)); } }
The Spring container starts normally and can obtain two beans, FieldA and FieldB
The circular dependence of attributes is often asked in interviews. Generally speaking, it's not complicated, but it involves the initialization process of Spring Bean, so it feels complicated. I'll write a demo to demonstrate the whole process
The initialization process of Spring beans is actually quite complex. In order to facilitate understanding the Demo, I divide the initialization process of Spring beans into two parts
-
bean instantiation process, that is, call the constructor to create the object
-
The initialization process of a bean, that is, filling in various properties of the bean
After the bean initialization process is completed, the bean can be created normally
Let's start writing the Demo. The ObjectFactory interface is used to produce beans, which is the same as the interface defined in Spring
public interface ObjectFactory<T> { T getObject(); } public class DependencyDemo { // Initialized Bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // The factory corresponding to the Bean being initialized. At this time, the object has been instantiated private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // Store the Bean being initialized. The object is put in before it is instantiated private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); public <T> T getBean(Class<T> beanClass) throws Exception { // The class name is the name of the Bean String beanName = beanClass.getSimpleName(); // Already initialized, or initializing Object initObj = getSingleton(beanName, true); if (initObj != null) { return (T) initObj; } // The bean is being initialized singletonsCurrentlyInCreation.add(beanName); // Instantiate bean Object object = beanClass.getDeclaredConstructor().newInstance(); singletonFactories.put(beanName, () -> { return 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)); } // Initialization complete singletonObjects.put(beanName, object); singletonsCurrentlyInCreation.remove(beanName); return (T) object; } /** * allowEarlyReference The meaning of the parameter is whether Spring allows circular dependency. The default value is true * Therefore, when allowrearlyreference is set to false, the project will fail to start when there is a circular dependency */ public Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); } } } } return singletonObject; } /** * Determine whether the bean is being initialized */ public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName); } }
Test a wave
public static void main(String[] args) throws Exception { DependencyDemo dependencyDemo = new DependencyDemo(); // Pretend to scan out the object Class[] classes = {A.class, B.class}; // Pretend that the project initializes all bean s for (Class aClass : classes) { dependencyDemo.getBean(aClass); } // true System.out.println( dependencyDemo.getBean(B.class).getA() == dependencyDemo.getBean(A.class)); // true System.out.println( dependencyDemo.getBean(A.class).getB() == dependencyDemo.getBean(B.class)); }
Isn't it simple? We only used two map s to solve the Spring circular dependency
Two maps can handle circular dependency, so why does Spring use three maps?
The reason is also very simple. When we get the corresponding ObjectFactory from singletonFactories according to BeanName, then we call getObject() to return the corresponding Bean. In our example, the implementation of ObjectFactory is simple, that is, directly returning objects that are instantiated. But in Spring, it is not so simple. The execution process is rather complicated. In order to avoid getting ObjectFactory and calling getObject() each time, we simply cache the objects created by ObjectFactory, so that we can improve efficiency.
For example, a depends on B and C, and B and C depend on A. if caching is not done, initializing B and C will call the getObject() method of ObjectFactory corresponding to a. If you do caching, you only need to call B or C once.
Knowing the idea, we changed the above code and added a cache.
public class DependencyDemo { // Initialized Bean private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // The factory corresponding to the Bean being initialized. At this time, the object has been instantiated private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // Beans produced by the factory corresponding to the cache Bean private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // Store the Bean being initialized. The object is put in before it is instantiated private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16)); public <T> T getBean(Class<T> beanClass) throws Exception { // The class name is the name of the Bean String beanName = beanClass.getSimpleName(); // Already initialized, or initializing Object initObj = getSingleton(beanName, true); if (initObj != null) { return (T) initObj; } // The bean is being initialized singletonsCurrentlyInCreation.add(beanName); // Instantiate bean Object object = beanClass.getDeclaredConstructor().newInstance(); singletonFactories.put(beanName, () -> { return 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); singletonsCurrentlyInCreation.remove(beanName); earlySingletonObjects.remove(beanName); return (T) object; } /** * allowEarlyReference The meaning of the parameter is whether Spring allows circular dependency. The default value is true */ public 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; } }
What we as like as two peas in getSingleton principle is exactly the same as that in org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean).
Sum up a wave
- When taking bean s, first get them from singletonObjects (first level cache)
- If it cannot be obtained and the object is being created, it is obtained from the earlySingletonObjects (L2 cache)
- If you still can't get it, you can get it from singletonFactories (Level 3 cache), then put the obtained object into earlySingletonObjects (Level 2 cache), and clear the singletonFactories (Level 3 cache) corresponding to the bean
- After the bean is initialized, put it into singletonObjects (first level cache) and clear the earlySingletonObjects (second level cache) corresponding to the bean