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