Announcement Announcement Module
Collapse
No announcement yet.
Circular Dependencies Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Circular Dependencies

    Hi,

    I a problem where I would like to have two service beans with cross reference to each other.

    In my case, personServiceBean needs orgServiceBean and orgServiceBean needs personServiceBean.

    I have declared these as properties and declare everything in my XML file, but when I run my application I get a "org.springframework.beans.factory.FactoryBeanCirc ularReferenceException".

    Is there a way to get around this problem?

  • #2
    Would you need a third bean that references the other two?

    Comment


    • #3
      No.

      Comment


      • #4
        Can you post the code for config and the structural code of the beans please?

        Comment


        • #5
          This is the bean definition. The serviceProxyTemplate is a TransactionProxyFactoryBean.

          Code:
              <bean id="personService" parent="serviceProxyTemplate">
          		<property name="target">
          			<bean class="biz.ist.atlas.service.PersonService"  >
                          <property name="orgService">
                              <ref bean="organisationService"/>
                          </property>
          			</bean>
          		</property>
          	</bean>
          
          	<bean id="organisationService" parent="serviceProxyTemplate">
          		<property name="target">
          			<bean class="biz.ist.atlas.service.OrganisationService">
          				<property name="personService">
          					<ref bean="personService"/>
          				</property>
          			</bean>
          		</property>
          	</bean>
          This is the error message:

          Error creating bean with name 'biz.ist.atlas.service.PersonService' defined in resource [/WEB-INF/applicationContext.xml] of ServletContext: Can't resolve reference to bean 'organisationService' while setting property 'orgService'; nested exception is org.springframework.beans.factory.BeanCreationExce ption: Error creating bean with name 'biz.ist.atlas.service.OrganisationService' defined in resource [/WEB-INF/applicationContext.xml] of ServletContext: Can't resolve reference to bean 'personService' while setting property 'personService'; nested exception is org.springframework.beans.factory.FactoryBeanCircu larReferenceException: Error creating bean with name 'personService': FactoryBean returned null object: not fully initialized due to circular bean reference

          Comment


          • #6
            I see now. Unfortunately, because of the way FactoryBeans work they need to be fully initializaed before they can be used to create the actual bean. Since a circular reference between two FactoryBeans will always prevent both from being initialized it is not permitted.

            To get around this, have one of your beans implement ApplicationContextAware, this way you can look up the bean using ApplicationContext.getBean() yourself. Alternatively, you just wire up one of the dependencies using DI and then create a BeanFactoryPostProcessor to wire up the other for you after both FactoryBeans have been created. This is a slightly more complex solution, but is a little bit more elegant.

            Rob

            Comment


            • #7
              Originally posted by robh
              I see now. Unfortunately, because of the way FactoryBeans work they need to be fully initializaed before they can be used to create the actual bean. Since a circular reference between two FactoryBeans will always prevent both from being initialized it is not permitted.
              Sadly, I have this same problem when only using one FactoryBean...

              Code:
              ...
                  <bean id="lookupTarget" class="com.company.commons.persist.HibernateDataCallback">
                      <property name="sessionFactory">
                          <ref local="sessionFactory"/>
                      </property>
                      <property name="transformHibernateCollections">
                          <value>true</value>
                      </property>
                      <property name="interceptor">
                          <bean class="com.company.commons.persist.audit.AuditInterceptor">
                              <property name="dataCallback">
                                  <ref local="dataCallback"/>
                              </property>
                          </bean>
                      </property>
                  </bean>
              
                  <bean id="dataCallback" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
                      <property name="transactionManager">
                          <ref local="transactionManager"/>
                      </property>
                      <property name="target">
                          <ref local="lookupTarget"/>
                      </property>
                      <property name="transactionAttributes">
                          <props>
                              <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
                              <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
                              <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
                              <prop key="store*">PROPAGATION_REQUIRED</prop>
                          </props>
                      </property>
                  </bean>
              
              ...
              Causes the following exception...

              Error creating bean with name 'dataCallback': FactoryBean returned null object: not fully initialized due to circular bean reference
              org.springframework.beans.factory.BeanCreationExce ption: Error creating bean with name 'com.company.commons.persist.audit.AuditIntercepto r' defined in class path resource


              Is this also not possible? I'm trying to set a Hibernate Interceptor (which is NOT a FactoryBean) on my HibernateDataCallback, which is exposed as a TransactionProxyFactoryBean. The interceptor requires a reference to the dataCallback itself so that it can add or remove rows during hibernate transactions...

              Thanks! Keep up the great work!
              -Alex

              Comment


              • #8
                One possible solution

                I had the same problem. And while I'd like to eliminate the circular dependency, it's not really an option given my timeframe. Here is the solution I came up with. It's not all that pretty, but it seems to work for what I'm doing at the moment.

                NOTE: I initially tried just setting the reference directly by calling getBean in the postProcessBeforeInitialization method, but the problem is that if you do that you still run into the circular dependency. The workaround is just to create a dynamic proxy that performs the bean lookup for every method invocation. It's a slight performance hit, and could have some implications if you're not using singleton services, but otherwise it should work.

                Code:
                public class CircularReferenceConfigurer
                	implements BeanFactoryPostProcessor, BeanPostProcessor &#123;
                
                	private String _sourceBeanName;
                	private String _sourceBeanPropertyName;
                	private String _targetBeanName;
                	private String _targetBeanInterface;
                	private BeanFactory _factory;
                
                	public CircularReferenceConfigurer&#40;&#41; &#123;
                	&#125;
                
                	public void postProcessBeanFactory&#40;ConfigurableListableBeanFactory factory&#41;
                		throws BeansException &#123;
                
                		if &#40;_sourceBeanName == null&#41; &#123;
                			throw new BeanCreationException&#40;"sourceBeanName must be defined"&#41;;
                		&#125;
                		if &#40;_sourceBeanPropertyName == null&#41; &#123;
                			throw new BeanCreationException&#40;"sourceBeanPropertyName must be defined"&#41;;
                		&#125;
                		if &#40;_targetBeanName == null&#41; &#123;
                			throw new BeanCreationException&#40;"targetBeanName must be defined"&#41;;
                		&#125;
                		if &#40;_targetBeanInterface == null&#41; &#123;
                			throw new BeanCreationException&#40;"targetBeanInterface must be defined"&#41;;
                		&#125;
                		_factory = factory;
                	&#125;
                
                	public Object postProcessBeforeInitialization&#40;Object bean, String beanName&#41;
                		throws BeansException &#123;
                		return bean;
                	&#125;
                
                	public Object postProcessAfterInitialization&#40;Object bean, String beanName&#41;
                		throws BeansException &#123;
                
                		// If the bean created is not equal to the source bean, don't do anything
                		if &#40;!_sourceBeanName.equals&#40;beanName&#41;&#41; &#123;
                			return bean;
                		&#125;
                
                		// Create the dynamic proxy for the target bean
                		Class targetInterface = null;
                		try &#123;
                			targetInterface = Class.forName&#40;_targetBeanInterface&#41;;
                		&#125; catch &#40;ClassNotFoundException e1&#41; &#123;
                			throw new BeanCreationException&#40;
                				"unable to locate targetBeanInterface &#91;"
                					+ _targetBeanInterface
                					+ "&#93;",
                				e1&#41;;
                		&#125;
                		Object targetBeanProxy =
                			BeanFactoryBeanProxy.newInstance&#40;
                				targetInterface,
                				_factory,
                				_targetBeanName&#41;;
                
                		// Now set the dependency
                		try &#123;
                			BeanInfo beanInfo = null;
                			beanInfo = Introspector.getBeanInfo&#40;bean.getClass&#40;&#41;&#41;;
                			PropertyDescriptor&#91;&#93; propertyDescriptors =
                				beanInfo.getPropertyDescriptors&#40;&#41;;
                			for &#40;int i = 0; i < propertyDescriptors.length; i++&#41; &#123;
                				if &#40;_sourceBeanPropertyName
                					.equals&#40;propertyDescriptors&#91;i&#93;.getName&#40;&#41;&#41;&#41; &#123;
                					Method setter = propertyDescriptors&#91;i&#93;.getWriteMethod&#40;&#41;;
                					setter.invoke&#40;bean, new Object&#91;&#93; &#123; targetBeanProxy &#125;&#41;;
                					return bean;
                				&#125;
                			&#125;
                			throw new BeanCreationException&#40;
                				"no such property &#91;"
                					+ _sourceBeanPropertyName
                					+ "&#93; found for object&#58; "
                					+ bean&#41;;
                		&#125; catch &#40;IllegalAccessException e&#41; &#123;
                			throw new BeanCreationException&#40;
                				"unable to set property &#91;"
                					+ _sourceBeanPropertyName
                					+ "&#93; on sourceBean &#91;"
                					+ _sourceBeanName
                					+ "&#93;",
                				e&#41;;
                		&#125; catch &#40;InvocationTargetException e&#41; &#123;
                			throw new BeanCreationException&#40;
                				"unable to set property &#91;"
                					+ _sourceBeanPropertyName
                					+ "&#93; on sourceBean &#91;"
                					+ _sourceBeanName
                					+ "&#93;",
                				e&#41;;
                		&#125; catch &#40;IllegalArgumentException e&#41; &#123;
                			throw new BeanCreationException&#40;
                				"unable to set property &#91;"
                					+ _sourceBeanPropertyName
                					+ "&#93; on sourceBean &#91;"
                					+ _sourceBeanName
                					+ "&#93;",
                				e&#41;;
                		&#125; catch &#40;IntrospectionException e&#41; &#123;
                			throw new BeanCreationException&#40;
                				"unable to set property &#91;"
                					+ _sourceBeanPropertyName
                					+ "&#93; on sourceBean &#91;"
                					+ _sourceBeanName
                					+ "&#93;",
                				e&#41;;
                		&#125;
                	&#125;
                
                	public String getSourceBeanName&#40;&#41; &#123;
                		return _sourceBeanName;
                	&#125;
                	public String getTargetBeanName&#40;&#41; &#123;
                		return _targetBeanName;
                	&#125;
                	public void setSourceBeanName&#40;String string&#41; &#123;
                		_sourceBeanName = string;
                	&#125;
                	public void setTargetBeanName&#40;String string&#41; &#123;
                		_targetBeanName = string;
                	&#125;
                	public String getSourceBeanPropertyName&#40;&#41; &#123;
                		return _sourceBeanPropertyName;
                	&#125;
                	public void setSourceBeanPropertyName&#40;String string&#41; &#123;
                		_sourceBeanPropertyName = string;
                	&#125;
                	public String getTargetBeanInterface&#40;&#41; &#123;
                		return _targetBeanInterface;
                	&#125;
                	public void setTargetBeanInterface&#40;String string&#41; &#123;
                		_targetBeanInterface = string;
                	&#125;
                
                	private static class BeanFactoryBeanProxy implements InvocationHandler &#123;
                
                		private BeanFactory _factory;
                		private String _beanName;
                
                		private BeanFactoryBeanProxy&#40;BeanFactory factory, String beanName&#41; &#123;
                			_factory = factory;
                			_beanName = beanName;
                		&#125;
                
                		public static Object newInstance&#40;
                			Class theInterface,
                			BeanFactory factory,
                			String beanName&#41; &#123;
                			Class&#91;&#93; proxyInterfaces = new Class&#91;&#93; &#123; theInterface &#125;;
                			return Proxy.newProxyInstance&#40;
                				theInterface.getClassLoader&#40;&#41;,
                				proxyInterfaces,
                				new BeanFactoryBeanProxy&#40;factory, beanName&#41;&#41;;
                		&#125;
                
                		public Object invoke&#40;Object proxy, Method m, Object&#91;&#93; args&#41;
                			throws Throwable &#123;
                			Object result;
                			try &#123;
                				// Before the method, lookup the target bean
                				Object target = _factory.getBean&#40;_beanName&#41;;
                				result = m.invoke&#40;target, args&#41;;
                			&#125; catch &#40;InvocationTargetException e&#41; &#123;
                				throw e.getTargetException&#40;&#41;;
                			&#125; catch &#40;Exception e&#41; &#123;
                				throw e;
                			&#125; finally &#123;
                				// After the method has completed, but before the argument
                				// is returned
                			&#125;
                			return result;
                		&#125;
                
                	&#125;
                
                &#125;

                Then in your configuration file, you use it as follows:

                Code:
                	<bean id="fooService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
                		<property name="target">
                			<bean class="fooServiceImpl">
                				<property name="barService">
                					<ref bean="barService" />
                				</property>
                			</bean>
                		</property>
                	</bean>
                
                	<bean id="barService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
                		<property name="target">
                			<bean id="barServiceImpl" class="barServiceImpl">
                				<!-- circular dependency
                					<property name="fooService">
                					<ref bean="fooService" />
                					</property>
                				-->
                			</bean>
                		</property>
                	</bean>
                
                	<bean id="barServiceRefConfigurer" class="CircularReferenceConfigurer">
                		<property name="sourceBeanName">
                			<value>barServiceImpl</value>
                		</property>
                		<property name="sourceBeanPropertyName">
                			<value>fooService</value>
                		</property>
                		<property name="targetBeanName">
                			<value>fooService</value>
                		</property>
                		<property name="targetBeanInterface">
                			<value>FooService</value>
                		</property>
                	</bean>

                Comment

                Working...
                X