Tuesday, November 27, 2007

Using a lazy proxy to avoid Spring dependency cycles

Ever gotten a dependency cycle in your Spring configuration?

How about using the Hibernate Annotations and @Configurable together?
  1. Hibernate will instantiate a single instance of each Entity class and ask for the default key value.
  2. But when the first instance is instantiated @Configurable will try to Spring configure that instance.
  3. It that Entity depends on anything that leads back to Hibernate... Bang!
I like this quote from Rod Johnson: "Ouch: this is nastier." That about sums it up. Oh, and that quote is from 2004.

I created a LazyProxyFactoryBean to allow me the chance to not break the cycle, but at least lazy resolve it. Combining a Spring FactoryBean with a dynamic proxy does the trick. Hope this can help someone else out there.

Here is a failing example with cycles:

<bean id="serviceA" class="com.ServiceA">
<property name="serviceB" ref="serviceB"/>
</bean>

<bean id="serviceB" class="com.ServiceB">
<property name="serviceA" ref="serviceA"/>
</bean>


Here is a modified version with a lazy proxy inserted (notice that proxy property is a value, not a ref):

<bean id="serviceA" class="com.ServiceA">
<property name="serviceB" ref="serviceB"/>
</bean>

<bean id="serviceB" class="com.ServiceB">
<property name="serviceA" ref="serviceAProxy"/>
</bean>

<bean id="serviceAProxy" class="com.LazyProxyFactoryBean">
<property name="serviceA" value="serviceA"/>
</bean>


Finally, here is the source code:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;

public class LazyProxyFactoryBean implements FactoryBean, BeanFactoryAware {

private String beanName;
private BeanFactory beanFactory;
private Object proxyObject;
private Object realObject;

public LazyProxyFactoryBean() {
}

public Object getRealObject() throws Exception {
if (this.realObject == null)
this.realObject = this.beanFactory.getBean(this.beanName);

return this.realObject;
}

public Object getObject() throws Exception {
Class[] ifcs = getProxyInterfaces();
if (ifcs == null) {
throw new FactoryBeanNotInitializedException(
getClass().getName() + " does not support circular references");
}
if (this.proxyObject == null) {
this.proxyObject = Proxy.newProxyInstance(getClass().getClassLoader(), ifcs,
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return method.invoke(getRealObject(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
});
}
return this.proxyObject;
}

public Class getObjectType() {
return beanFactory.getType(beanName);
}

public boolean isSingleton() {
return false;
}

public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}

protected Class[] getProxyInterfaces() {
Class type = getObjectType();

if (type != null && type.isInterface()) {
return new Class[] {type};
} else if (type != null) {
return type.getInterfaces();
} else {
return null;
}
}

public void setBeanName(String beanName) {
this.beanName = beanName;
}
}

2 comments:

IanR said...

You can do this without writing a custom class - Spring has built-in support for this technique via the AOP proxy mechanism:

<bean id="serviceA" class="com.ServiceA" lazy-init="true">
<property name="serviceB" ref="serviceB"/>
</bean>

<bean id="serviceB" class="com.ServiceB">
<property name="serviceA" ref="serviceAProxy"/>
</bean>

<bean id="serviceAProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource">
<bean class="org.springframework.aop.target.LazyInitTargetSource">
<property name="targetBeanName">
<idref local="serviceA"/>
</property>
</bean>
</property>
</bean>

The LazyInitTargetSource JavaDoc has the full details.

Anonymous said...

Thanks for your tip, it was really helpful!