1. Preface
We know that Spring can be lazy to load, that is, instantiate the bean when it is actually used. Of course, this is not the case. For example, configuring the lazy-init property of a bean can control the loading time of Spring. Now the performance, memory, etc. of the machine are relatively high, and basically do not use lazy loading. The bean is loaded at the start of the container, and the startup time is a little longer. In this way, when the bean is actually obtained for business use, a lot of burden can be reduced. This will be analyzed later. When we use beans, the most direct way is to get them from Factroy, which is the source of loading bean instances.
Recently, I encountered a strange problem when working on projects, that is, the accuracy of bean dependency injection is related to the order of bean direct injection, but under normal circumstances it has nothing to do with the order. If you are in a hurry, let me tell you one by one.
2. Ordinary Bean cyclic dependency-not related to the injection order
2.1 Circular dependency examples and principles
public class BeanA {private BeanB beanB;public BeanB getBeanB() { return beanB;}public void setBeanB(BeanB beanB) { this.beanB = beanB;}} public class BeanB {private BeanA beanA;public BeanA getBeanA() { return beanA;}public void setBeanA(BeanA beanA) { this.beanA = beanA;}}<bean id="beanA"><property name="beanB"> <ref bean="beanB" /></property></bean>
<bean id="beanB"><property name="beanA"> <ref bean="beanA" /></property></bean>
The above circular dependency injection works normally because Spring provides EarlyBeanReference function. First, there is a concurrent map named singletonObjects in Spring to store all instantiated and initialized beans, while singletonFactories is used to store bean information (beanName, and a callback factory) that needs to be solved. When instantiating beanA, getBean(“beanA”); first, see if there is beanA in singletonObjects, it will return:
(1)
Object sharedInstance = getSingleton(beanName);//getSingleton(beanName,true);if (sharedInstance != null && args == null) {if (logger.isDebugEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); }} // If it is a normal bean, it returns sharedInstance.getObject();bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);} protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory singletonFactory = (ObjectFactory) this.singletonFactory.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactory.remove(beanName); } } } return (singletonObject != NULL_OBJECT ? singletonObject : null);} At the beginning, there is definitely no beanA, so if allowCircularReferences=true is set (default is true) and the current bean is single-piece and the bean is currently being created, then before initializing the attribute, put the bean information into the singletonFactories single-piece map:
(2)
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, new ObjectFactory() { public Object getObject() throws BeansException { return 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.singletonFactory.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); }}} Then inject the beanB into the instance attribute. When injecting the attribute, getBean(“beanB”) and find that beanB is not in singletonObjects, it will instantiate beanB, then put singletonFactories, then inject the beanA, and then trigger getBean(“beanA”); at this time, getSingleton will return the instantiated beanA. After the beanB is initialized, add beanB to singletonObjects and then return, then beanA is initialized, add beanA to singletonObjects and then return
2.2 Switches that allow loop dependencies
public class TestCircle2 {private final static ClassPathXmlApplicationContext moduleContext;private static Test test;static { moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"}); moduleContext.setAllowCircularReferences(false); test = (Test) moduleContext.getBean("test");}public static void main(String[] args) { System.out.println(test.name);}}There is a property allowCircularReferences in the ClassPathXmlApplicationContext class to control whether circular dependencies are allowed to be true by default. After setting it to false, it is found that circular dependencies can still run normally. Look at the source code:
public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException {this(configLocations, true, null);} public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) { refresh();}} public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) { refresh();}} Know that the container will be refreshed when the ClassPathXmlApplicationContext is constructed by default.
The refresh method will call refreshBeanFactory:
protected final void refreshBeanFactory() throws BeansException {if (hasBeanFactory()) { destroyBeans(); closeBeanFactory();}try { // Create bean factory DefaultListableBeanFactory beanFactory = createBeanFactory(); //Customize bean factory properties customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; }}catch (IOException ex) { throw new ApplicationContextException( "I/O error parsing XML document for application context [" + getDisplayName() + "]", ex);}} protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {if (this.allowBeanDefinitionOverriding != null) { beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding.booleanValue());}if (this.allowCircularReferences != null) { beanFactory.setAllowCircularReferences(this.allowCircularReferences.booleanValue());}} You will know here that before we call moduleContext.setAllowCircularReferences(false) , the customizeBeanFactory of the customizeBeanFactory left by spring has been executed. The final reason is that before calling the settings, the bean factory has refreshed, so the test code is changed to:
public class TestCircle {private final static ClassPathXmlApplicationContext moduleContext;private static Test test;static { //Initialize the container context, but do not refresh the container moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"},false); moduleContext.setAllowCircularReferences(false); //Refresh the container moduleContext.refresh(); test = (Test) moduleContext.getBean("test");}public static void main(String[] args) { System.out.println(test.name);}}Now the test will throw an exception:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanA' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanB' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
3. Factory beans and ordinary beans cyclic dependencies - related to injection order
3.1 Test code
Factory bean
public class MyFactoryBean implements FactoryBean,InitializingBean{private String name;private Test test;public String getName() { return name;}public void setName(String name) { this.name = name;}public DependentBean getDepentBean() { return dependentBean;}public void setDepentBean(DependentBean dependentBean) { this.dependentBean = dependentBean;}private DependentBean dependentBean;public Object getObject() throws Exception { return test;}public Class getObjectType() { // TODO Auto-generated method stub return Test.class;}public boolean isSingleton() { // TODO Auto-generated method stub return true;}public void afterPropertiesSet() throws Exception { System.out.println("name:" + this.name); test = new Test(); test.name = dependentBean.doSomething() + this.name;}} For simplicity, just write a public variable
public class Test {public String name;} public class DependentBean {public String doSomething(){ return "hello:";}@Autowiredprivate Test test;} xml configuration
<bean id="test"><property name="dependentBean"> <bean></bean></property><property name="name" value="zlx"></property></bean>
The factory Bean MyFactoryBean function is to wrap the Test class. First, set properties for MyFactoryBean, then create a Test instance in the afterPropertiesSet method of MyFactoryBean and set the properties. Instantiating MyFactoryBean will eventually call the getObject method to return the created Test object. Here MyFactoryBean depends on DepentBean, and dependentBean itself depends on Test, so this is a circular dependency
test:
public class TestCircle2 {private final static ClassPathXmlApplicationContext moduleContext;private static Test test;static { moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"}); test = (Test) moduleContext.getBean("test");}public static void main(String[] args) { System.out.println(test.name);}}result:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.alibaba.test.circle.DependentBean#1c701a27': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.alibaba.test.circle.Test com.alibaba.test.circle.DependentBean.test; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'test': FactoryBean which is currently in creation returned null from getObject
3.2 Analysis of reasons
When instantiating test getBean(“test”) will be triggered, and it will see if the current bean exists
If it does not exist, create an instance of Test. After the creation, the current bean information will be placed in the singletonFactories single-piece map.
Then inject the attribute dependentBean into the instance. When the attribute injection is used, getBean(“depentBean”) will be used.
If you find that the dependentBean does not exist, you will instantiate the dependentBean and then put it in singletonFactories.
Then autowired injection test, and then getBean(“test”); at this time (1) getSingleton returns the instantiated test. Since test is a factory bean, return test.getObject();
MyFactoryBean's afterPropertiesSet has not been called yet, so test.getObject() returns null.
The following are the following Spring bean creation process:
getBean()->Create instance->autowired->set property->afterPropertiesSet
That is, calling the getObject method was called earlier than the afterPropertiesSet method.
Then let's modify MyFactoryBean to be as follows:
public Object getObject() throws Exception {// TODO Auto-generated method stubif(null == test){ afterPropertiesSet();}return test;} public void afterPropertiesSet() throws Exception {if(null == test){ System.out.println("name:" + this.name); test = new Test(); test.name = dependentBean.doSomething() + this.name;}} That is, if you judge within getObject first, it is better to test==null Then call afterPropertiesSet, and then if test==null is creating a Test instance within afterPropertiesSet, it looks good, and I really want to solve our problem. But in fact, it still doesn't work, because afterPropertiesSet uses dependentBean internally, and at this time depentBean=null .
3.3 Thinking about how to solve it
3.2 Analysis reason is that the MyFactoryBean was first created, and the DepentBean was created during the process of creating the MyFactoryBean. When creating the DepentBean, an instance of the autowired MyFactoryBean is required. Then, the getObject method is called before calling afterPropertiesSet, so null is returned.
So if you create a DepentBean first, and then create a MyFactoryBean? The following analysis is made:
First, the DepentBean will be instantiated and added to singletonFactories
The DepentBean instance will autowired Test, so the Test instance will be created first.
Create a Test instance and then join singletonFactories
The Test instance will inject the DepentBean instance at attributes, so it will getBean(“depentBean”);
getBean(“depentBean”) finds that there is already a dependentBean in singletonFactories, and returns the dependentBean object
Because dependentBean is not a factory bean, it directly returns dependentBean
Test instance will be injected into the DepentBean instance successfully, Test instance initialization OK
DepentBean instance autowired Test instance OK
According to this analysis, it is feasible to create a DepentBean first, and then instantiate the MyFactoryBean. Modify the xml to the following:
<bean id="dependentBean"></bean><bean id="test"><property name="dependentBean"> <ref bean="dependentBean" /> </property><property name="name" value="zlx"></property></bean>
Test run results:
name:zlx
hello:zlx
If it is really OK, then according to this analysis, if the above XML configuration is adjusted, it will definitely make an error, because the test was created earlier than the dependentBean, and this is true after the test. In addition, it can be imagined that when a factory bean relies on a factory bean, it will inevitably fail regardless of the order of declaration.
3.3 A thought
The above first injects the dependentBean that needs to be used in MyFactoryBean, and then injects the MyFactoryBean, and the problem is solved. So if you need to use the created object with id="test" in another bean, how should this bean be injected?
Will it succeed in a similar way? Leave it for everyone to think^^
public class UseTest {@Autowiredprivate Test test;}<bean id="useTest"></bean><bean id="dependentBean"></bean><bean id="test"><property name="dependentBean"> <ref bean="dependentBean" /> </property><property name="name" value="zlx"></property></bean>
4. Summary
When ordinary beans are interdependent, the order of bean injection is not related, but when factory beans and ordinary beans are interdependent, the ordinary bean must be instantiated first. This is because of the particularity of factory beans, that is, it has a getObject method.
Okay, the above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.