How about using the Hibernate Annotations and @Configurable together?
- Hibernate will instantiate a single instance of each Entity class and ask for the default key value.
- But when the first instance is instantiated @Configurable will try to Spring configure that instance.
- It that Entity depends on anything that leads back to Hibernate... Bang!
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:
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.
Thanks for your tip, it was really helpful!
Post a Comment