Wednesday, March 7, 2012

Resolve circular dependency in Spring

It is discussed clearly in Managing Circular Dependencies in Spring.

We used to deal with it in init() method. For example, A<->B, inject B into A, and in A's init(), call B.setA(this). This approach has two drawbacks. Not only the interface of B must expose an additional setA method; but also *this* refers to the raw instance of A, rather than a proxied (wrapped) instance of A. That means, if the method of A requires AOP proxy such as transaction interceptor, you will get Hibernate no session bounded exception. Remember, the Spring bean (applicationContext.getBean("xxx")) provides us both dependency injection and AOP proxy.

The article describes two solutions:
1. Inject ApplicationContext to one bean through ApplicationContextAware interface and look up the peer bean
2. Use a BeanPostProcessor to wire up the beans after they are instantiated. This is preferred since it allows the container to handle this, rather than the bean itself.

The implementation in the above post seems to be problematic. I always run into BeanCurrentlyInCreationException. I revised it as follows, basically, the passed in initialized bean should be target bean, and we know both beans are ready at this point. Also, I added support for proxy and factory bean.

<bean id="circularDependencyBeanPostProcessor" class="CircularDependencyBeanPostProcessor">
  <property name="config">
   <map>
    <entry key="templateManager">  
     <props>
      <prop key="policyDomainManager">templateManager</prop>  
     </props>
    </entry>
   </map>
  </property>
 </bean>
public Object postProcessAfterInitialization(Object targetBean, String beanName) {
  if (config == null) {
   return targetBean;
  }
  Map dependentConfig = (Map) config.get(beanName);
  if (dependentConfig != null) {
   try {
    if (targetBean instanceof FactoryBean) {
     targetBean = ((FactoryBean) targetBean).getObject();
    }
    
    for (Object sourceBeanName : dependentConfig.keySet()) {
     Object sourceBean = factory.getBean((String) sourceBeanName);
     
     // Jdk dynamic proxy is interface based, generally we don't want
     // to expose those set methods such as setPolicyDomainMgr()
     // in the interface. In this case, we inject the dependent bean
     // into its target object instead.
     if (AopUtils.isJdkDynamicProxy(sourceBean)) {
      sourceBean = ((Advised) sourceBean).getTargetSource().getTarget();
     }
     String propertyName = (String)dependentConfig.get(sourceBeanName);
    
     BeanInfo beanInfo = Introspector.getBeanInfo(sourceBean.getClass());
     PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
     if (propertyDescriptors != null) {
      for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
       if (propertyName.equals(propertyDescriptor.getName())) {
        Method setter = propertyDescriptor.getWriteMethod();
        setter.invoke(sourceBean, targetBean);
        break;
       }
      }
     }
    }

   } catch (IntrospectionException ie) {
    log.error("IntrospectionException", ie);
   } catch (Exception e) {
    log.error(e);
   }

  }
  return targetBean;
 }

No comments: