Spring Pitfalls
2011-09-14
Spring is the most common used
IOC container. Due to the many
functionalities there are many source of errors
not visible at first sight. A typical problem is
a dependency tending to an incorrect created
bean instance.
Postprocessors
Spring has the feature of allowing beans of the context to manipulate other beans, the bean factory or the bean definitions. To do so, Spring needs to create bean instances of these processor beans, Depending on the processor type (BeanPostProcessor, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor) this will be at an early point of lifecicle. This may result in the following problems:
* The bean need processing but has not been processed (like
* The bean has a dependency which also need to be created and neither processed
Unfortunately these effects occur unexpected in most cases. The spring context of a complex application typically contains some postprocessors, some of spring itself, some from libraries like CXF. These processors often instantiate partial bean trees in advance without causing any trouble. But adding a new dependency at any position of this tree may result in a problem without any indication of the reason.
Searching Beans
If you search for beans of a type from a
If this type check is done by a post processor you will run in the same problems as above.
Effects
These early instantiated beans are not completly initialized. This may occur to hole trees of beans. The effects are:
*
* access to a database without transaction or session due to no
Depending on the processing skipped these problems may occur on context startup or later when the bean is accessed.
Solutions
Some problems originated by missing post processing may be solved by implementing
This will not help if you have dependent beans instantiated early by a
This
becomes
The advantage of this solution is the lack of changing any existing bean or test code.
The problem will remain if the dependency will be used on initialization of the bean like at afterPropertiesSet(). In this case the DecouplingFactoryBean is useless. In this case try to change the bean code to use lazy init instead.
Postprocessors
Spring has the feature of allowing beans of the context to manipulate other beans, the bean factory or the bean definitions. To do so, Spring needs to create bean instances of these processor beans, Depending on the processor type (BeanPostProcessor, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor) this will be at an early point of lifecicle. This may result in the following problems:
* The bean need processing but has not been processed (like
@Autowire annotation)
* The bean has a dependency which also need to be created and neither processed
Unfortunately these effects occur unexpected in most cases. The spring context of a complex application typically contains some postprocessors, some of spring itself, some from libraries like CXF. These processors often instantiate partial bean trees in advance without causing any trouble. But adding a new dependency at any position of this tree may result in a problem without any indication of the reason.
Searching Beans
If you search for beans of a type from a
ListableBeanFactory the type
is determined by the class attribute of the
definition except for factory beans. The effective bean
type can be determined by a method call only. To do so the
factory bean has to be instantiated.
If this type check is done by a post processor you will run in the same problems as above.
Effects
These early instantiated beans are not completly initialized. This may occur to hole trees of beans. The effects are:
*
NullPointerException due to a missing
dependency not set by @Autowire
* access to a database without transaction or session due to no
@Transactional proxy
Depending on the processing skipped these problems may occur on context startup or later when the bean is accessed.
Solutions
Some problems originated by missing post processing may be solved by implementing
Ordered. But to determine
the correct value to run the processors in the
right order may need some experimental tries.
This will not help if you have dependent beans instantiated early by a
FactoryBean. To
avoid this instantiation you need to decouple tees
dependencies. A special FactoryBean
creating a Proxy may do the job. This
bean must not have any dependency to the bean to
decouple and need to offer the target bean type
without instantiating the target bean (a simple
property in the following solution):
public class DecouplingFactoryBean
implements FactoryBean, BeanFactoryAware, BeanClassLoaderAware, InvocationHandler
{
private String beanName;
private Class objectType;
private BeanFactory beanFactory;
private ClassLoader classLoader;
@Override
@SuppressWarnings("unchecked")
public T getObject() {
return (T) Proxy.newProxyInstance(classLoader, new Class[] {objectType}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
T bean = (beanName == null) ? beanFactory.getBean(objectType) : beanFactory.getBean(beanName, objectType);
return method.invoke(bean, args);
}
@Override
public Class getObjectType()
{
return objectType;
}
@Override
public boolean isSingleton() {
return true;
}
public void setBeanName(String name)
{
this.beanName = name;
}
@Required
public void setObjectType(Class type)
{
this.objectType = type;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
}
This
DecouplingFactoryBean may be added
between the factory bean and the dependency not to be
instantiated to avoid a direct dependency. Using the
proxy and getting the target in the invocation
handler in a lazy manner solves the problem.
<bean id="earlyBean" class="...">
<property name="lateDependency" ref="lateBean"/>
</bean>
<bean id="lateBean" class="my.late.Bean"/>
becomes
<bean id="earlyBean" class="...">
<property name="lateDependency">
<bean class="diergo.util.spring.DecouplingFactoryBean">
<property name="beanName" value="lateBean"/>
<property name="objectType" value="my.late.BeanTypeOrInterface"/>
</bean>
</property>
</bean>
<bean id="lateBean" class="my.late.Bean"/>
The advantage of this solution is the lack of changing any existing bean or test code.
The problem will remain if the dependency will be used on initialization of the bean like at afterPropertiesSet(). In this case the DecouplingFactoryBean is useless. In this case try to change the bean code to use lazy init instead.