Using Spring Beans from Non-Spring Classes
Join the DZone community and get the full member experience.
Join For FreeIn 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.
Opinions expressed by DZone contributors are their own.
Comments