Java programmer since 1996. J2SE, J2EE Expert, Technical Leader, Team Manager. Rich experience in infrastructure and architecture of distributed applications Pavel has posted 6 posts at DZone. You can read more from them at their website. View Full User Profile

Using Spring Beans from Non-Spring Classes

06.08.2012
| 8382 views |
  • submit to reddit

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:

 

  1. 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
  2. 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?
Finally I found somewhere in Internet (sorry, I do not remember the source of this idea!) such elegant solution: 

 

@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. 


Published at DZone with permission of its author, Pavel Bernshtam.

(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

 Thanks ,
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..

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.