Using Spring Beans from Non-Spring Classes
In our software both rich client and server are using Spring. And we have a problem - how to use spring beans from regular classes inside a rich client (no servlet/application context there!), which are not spring beans.
We had 2 ideas:
- Pass spring beans (or spring context) as constructore arguments of those classes at the time of their creation (inside spring beans). This looks very ugly, if you need to pass such argument through long chain of classes, until it will be used somewhere
- Use some static field to hold a reference to a spring singleton or spring context and a regular class will use such static reference. This solution looks bad too - when and who will initialize this static field?
@Service
public class StaticContextHolder implements BeanFactoryAware {
public static BeanFactory CONTEXT;
public StaticContextHolder() {
}
public static Object getBean(String s) throws BeansException {
return CONTEXT.getBean(s);
}
public static <T> T getBean(String s, Class<T> tClass) throws BeansException {
return CONTEXT.getBean(s, tClass);
}
public static <T> T getBean(Class<T> tClass) throws BeansException {
return CONTEXT.getBean(tClass);
}
public static Object getBean(String s, Object... objects) throws BeansException {
return CONTEXT.getBean(s, objects);
}
public static boolean containsBean(String s) {
return CONTEXT.containsBean(s);
}
@Override
public void setBeanFactory(BeanFactory applicationContext) throws BeansException {
logger.assertNull(CONTEXT, "CONTEXT is not null. Double Spring context creation?");
CONTEXT = applicationContext;
}
}Making this class factory aware bean, we ensure that it always be initialized at the time of Spring context creation.
However, for using in unit tests, this class should be modified.
We have tests, which creates a spring context, so I added to the class method
@PreDestroy
public void resetStatics() {
CONTEXT=null;
}
The second problem exists, when unit test is not creating a spring context, but the class under test is using StaticContextHolder.
To solve this problem I created a Fake Spring Context:
public class FakeBeanFactory implements BeanFactory {
private Map<String, Object> beans;
public FakeBeanFactory (Map<String, Object> beans) {
this.beans = beans;
}
@Override
public Object getBean(String s) throws BeansException {
return beans.get(s);
}
@Override
public <T> T getBean(String s, Class<T> tClass) throws BeansException {
return (T) beans.get(s);
}
@Override
public <T> T getBean(Class<T> tClass) throws BeansException {
return (T) beans.get(tClass.getName());
}
@Override
public Object getBean(String s, Object... objects) throws BeansException {
return beans.get(s);
}
@Override
public boolean containsBean(String s) {
return false; // I don't need it
}
@Override
public boolean isSingleton(String s) throws NoSuchBeanDefinitionException {
return false; // I don't need it
}
@Override
public boolean isPrototype(String s) throws NoSuchBeanDefinitionException {
return false; // I don't need it
}
// ....
}Now the initialization of aunit test looks like this:
@Before
public void init() {
Map<String,Object> beans = new Map<String,Object>();
beans.put("service-dependency", new MockupDependencyImpl());
StaticContextHolder.CONTEXT = new FakeBeanFactory(beans));
}
Now we have one more problem: prototype beans, which are created using init method, like
<bean id="PlanDefinitionReader" class="com.example.PlanDefinitionReader"
scope="prototype"
factory-method="createPlanDefinitionReader">
<constructor-arg index="0" value="null"/>
<constructor-arg index="1" value="null"/>
<constructor-arg index="2" value="null"/>
</bean>
To solve this problem, we'll add to the FakeBeanFactory one more Map:
Map<String s, Method m> initMethods
... and we'll override one more method:
public Object getBean(String s, Object... objects) throws BeansException {
return initMethods.get(s).invoke(null, objects);
}
We will initialize this Map by Method instances in the unit test initialization.
That's all. Something like this.
I'll be happy if somebody will advice of better solution to those problems.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





Comments
Fahmeed Nawaz replied on Tue, 2012/06/12 - 10:43am
i have done it on shell but the java and shell syntax are very different, its easy to write queries and retrieve data from mongodb shell but I am not able to do same with java driver..