Saturday, March 03, 2007

Accessing Spring beans from Legacy code

As I have blogged before, we have been trying to move to using the Spring framework in our web applications. We inherited a large body of working code which used a home-grown framework based on a combination of what Martin Fowler describes as the "JSP as Handler" web presentation pattern in his Enterprise Application Architecture book and Webwork action controllers. Our rationale for introducing Spring into this application is that maintenance and enhancement are very difficult and time-consuming with the existing architecture.

However, the conversion is not going to happen overnight, and neither can we mandate that all new development should stop as we move to the new architecture. It is also not possible to build and deploy the new functionality in separate web applications, because we don't have the resources to handle the deployment issues that such an approach would require. So our new Spring enabled code co-exists with the legacy code in the same web application.

One by-product of this setup is that we often end up developing non-GUI components using Spring, which need to be hooked into existing legacy code so we can reuse the legacy request flow. There are three ways we could do this:

  • Build the Spring bean manually in our legacy code using explict constructor and setter calls.
  • Scrap the legacy request flow and rebuild it using Spring.
  • Obtain a reference to the Spring bean that has been configured and built declaratively by the Spring container from the legacy code.

The first approach is feasible, but it is extra code that you don't need to write. Also, the first approach may result in strange null pointer exceptions if you missed something. It is also less efficient to construct them each time than having it be built once by Spring. The second approach is good if your long term goal is to get rid of the existing architecture altogether, but it is even more risky than the first approach because new code will generally contain new bugs. Also, because writing code takes time, you will have to build this extra time into your estimates. In my opinion, the last approach takes the minimum effort, code and poses the least risk, and can be accomplished quite simply, as I show below.

Our approach is to build a Singleton bean which is ApplicationContextAware and instantiate it in the Spring container by defining it in the spring-servlet.xml file in our web application. Because it is ApplicationContextAware, Spring would populate it with a reference to the ApplicationContext. Since we just want references to Spring beans from it, we implement a static getBean() method which would delegate to the ApplicationContext.getBean() method. Here is the code for our Singleton.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// SpringApplicationContext.java

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * Wrapper to always return a reference to the Spring Application Context from
 * within non-Spring enabled beans. Unlike Spring MVC's WebApplicationContextUtils
 * we do not need a reference to the Servlet context for this. All we need is
 * for this bean to be initialized during application startup.
 */
public class SpringApplicationContext implements ApplicationContextAware {

  private static ApplicationContext CONTEXT;

  /**
   * This method is called from within the ApplicationContext once it is 
   * done starting up, it will stick a reference to itself into this bean.
   * @param context a reference to the ApplicationContext.
   */
  public void setApplicationContext(ApplicationContext context) throws BeansException {
    CONTEXT = context;
  }

  /**
   * This is about the same as context.getBean("beanName"), except it has its
   * own static handle to the Spring context, so calling this method statically
   * will give access to the beans by name in the Spring application context.
   * As in the context.getBean("beanName") call, the caller must cast to the
   * appropriate target class. If the bean does not exist, then a Runtime error
   * will be thrown.
   * @param beanName the name of the bean to get.
   * @return an Object reference to the named bean.
   */
  public static Object getBean(String beanName) {
    return CONTEXT.getBean(beanName);
  }
}

The bean definition for this bean is just this one line:

1
  <bean id="springApplicationContext" class="com.mycompany.util.SpringApplicationContext"/>

In order to now get a reference to a bean named "mySpringBean" that has been defined in the Spring context, we will issue from somewhere in our legacy (non Spring-enabled) code:

1
2
  MySpringBean mySpringBean = (MySpringBean) SpringApplicationContext.getBean("mySpringBean");
  // do something with MySpringBean

The only caveat is that your DispatcherServlet must be loaded in web.xml before any component that needs to call the SpringApplicationContext.getBean(). This ensures that the ApplicationContext has finished loading and SpringApplicationContext has a populated reference to it.

This is the second time I had a need for this kind of bridge. The last time was at my previous employer where we were embedding Jive Forums software within our Spring enabled web application. We used the standard approach there, using the Spring ContextListener to store the ApplicationContext in the ServletContext, and then calling WebApplicationContextUtils.getApplicationContext(ServletContext) to get at the Spring context. I believe the approach I have outlined is better, since it is less code and there does not have to be a web container for this functionality to be available.

53 comments:

  1. This solution is very helpful, thank you.
    A.K.

    ReplyDelete
  2. You are welcome, and thanks for the feedback.

    ReplyDelete
  3. It's a very usefull solution. I am testing integration of a PHP Framework with Spring, using Quercus, and I spent two days until find this solution...with it, i can access Spring beans from PHP now...The advantage is it not need access the ServletContext (that, in my case, was not accessible). Thanks, thanks.

    ReplyDelete
  4. You are welcome, Ely. Glad it helped.

    ReplyDelete
  5. Thanks a lot for your useful post and wish all the best.
    I have one comment why CONTEXT is static? Isn't it better to keep bean convention?

    Thanks again, you made my day. You are an Angel.

    thabet@itu.dk

    ReplyDelete
  6. Thanks alot for your greate post. I feel very greateful.

    Wish you all the best:)

    ReplyDelete
  7. Hi Thabet, thanks very much for your kind words, and I am glad my post helped.

    ReplyDelete
  8. Hi Thabet, did not see your original question about why context is static. My use case was to be able to access Spring beans from within legacy code, so I just used a static SpringApplicationContext.getBean("foo") call from there. Had I followed the standard bean convention, I would have to instantiate this bean from within my legacy code and call the method, which would not have worked for me.

    ReplyDelete
  9. If one has 2 Spring containers running in the one JVM which include this bean then the static data will be overridden by the second instance in the second container.

    ReplyDelete
  10. Yes, that is a problem, one that one of my colleagues pointed out soon after I built this component. However, in our case, we use a single container in our JVM (with import resource calls to hook in multiple xml files), so we have never been bitten by this. I don't know of any good way to avoid this issue, short of making my target beans ApplicationContextAware themselves. What is your preferred approach, since you obviously work in apps where there are multiple Spring containers in the JVM?

    ReplyDelete
  11. Does this approach work from ejb which is outside Spring....i meant..accessing a spring enabled bean inside an ejb, where ejb is not in the spring framework..ejb acts as just a client accessing the spring bean...

    ReplyDelete
  12. SpringApplicationContext is ApplicationContextAware, so there needs to be a Spring container for it to be aware of. If you start up your application context in the same JVM as where you instantiate your ejb's this should work.

    ReplyDelete
  13. Thank you very much for your class code. It works fine !!!

    ReplyDelete
  14. If you want to get a Spring context from somewhere where you can get a ServletContext, try this:
    ServletContext theServletContext = ...;
    WebApplicationContext theWAC = WebApplicationContextUtils.getWebApplicationContext(
    theServletContext);

    ReplyDelete
  15. I have been looking for this for many days now.
    I found a very similar solution here (blog.jdevelop.eu/2008/07/06/access-the-spring-applicationcontext-from-everywhere-in-your-application/) and refactored it to your version. And here i am, finding out about it 2 minutes afterwards.
    Thanx anyway :D

    ReplyDelete
  16. Thanks for the comments, krizsan and anonymous.

    @krizsan: Yes, I know, and that is what I have used exclusively at my previous job. The reason we did this was because we had to integrate Spring with a legacy system which did not provide access to the Servlet objects in the service layer. But you are right, it is not good practice, and going forward, we are trying to minimize this approach in favor of grabbing it from the WebApplicationContextUtils call.

    @anonymous: Thanks for the pointer to Siegfried Bolz's post, I did read through it, the approach appears to be similar to mine.

    ReplyDelete
  17. Thanks for the article.
    When i did whatever you suggested, got an exception as NullPointerException at csdpolicy.util.SpringApplicationContext.getBean(SpringApplicationContext.java:39).

    From the exception it is clear that, applicationcontext is not set in my application.

    Added the followed line bean id="springApplicationContext" class="csdpolicy.util.SpringApplicationContext" in my applicationContext.xml and started my tomcat. And initialized as follows CSDRuleEngine csdRuleEngine = (CSDRuleEngine) SpringApplicationContext.getBean("csdRuleEngine"); in my sample code

    Is there anything i need to do ?

    ReplyDelete
  18. Hi Sirish, you are welcome and thanks for the feedback. From your description, it seems that you do have an application context because you have declared an applicationContext.xml file. I think the NPE you are getting may have to do with your CSDRuleEngine bean not being defined (or defined with a different name than what you are looking it up with) in the context.

    ReplyDelete
  19. Hi, I am using different approach. The code is like this:

    public class AppContext {

    private final static ApplicationContext context = new ClassPathXmlApplicationContext("/app_context.xml");

    public static IMyBean getMyBean() {
    return (IMyBean) context.getBean("myBean");
    }
    }

    There is no need to load the context through servlet.

    ReplyDelete
  20. Hi Yonghe, thanks for the tip. Indeed, that may be a better approach - the approach I describe doesn't work when an app is hosting multiple contexts - your approach will.

    ReplyDelete
  21. hey thanx man....your code is really helpful

    ReplyDelete
  22. Thanks Bapi, but please note that this will not work when your app is hosting multiple contexts. Try the approaches proposed by krizsan and yonghe in the comments.

    ReplyDelete
  23. Hi Sujit,
    How would I use Yonghe's solution for multiple contexts

    ReplyDelete
  24. Hi Prakash, I think Yonghe's solution allows you to restrict your set of beans by making the class load whatever it needs from the context file, rather than depend on the application context, so it "handles" the problem by working around it I guess :-).

    ReplyDelete
  25. Thank you Salmon, it was very helpful.

    ReplyDelete
  26. Thanks Hakan, glad it helped.

    ReplyDelete
  27. Exactly what I was looking for. So simple. Thanks.

    ReplyDelete
  28. Thanks Laran, although be aware that this approach is likely to bite you if you have more than one dispatcher servlet and if you decide to redefine a bean from one context to the other (the latest definition would win).

    ReplyDelete
  29. Thank you very much too, it works for me perfectly.

    ReplyDelete
  30. You are welcome Daniel. Although just remember the caveats of using this approach, if you have multiple application contexts, then you are probably better off configuring it in your web.xml as a filter.

    ReplyDelete
  31. It works! Thank you very much!

    ReplyDelete
  32. You are welcome, glad it helped.

    ReplyDelete
  33. Wow..!! Solved a long running problem in few minutes..!! Great job.!

    ReplyDelete
  34. Hi, you're welcome, glad it helped you.

    ReplyDelete
  35. This really worked for me too... I had to start a servlet and from there access spring managed beans...

    ReplyDelete
  36. Hi Shiva, glad it helped you.

    ReplyDelete
  37. Thanks Sujith. Simple and clean solution.
    As to somebody's question on multiple spring-context changing value of static slot, such an application should have multiple AppContextAwares like this to be used by classes in each of the context.

    ReplyDelete
  38. Thanks for the kind words, Sreekumar.

    ReplyDelete
  39. Very very useful!!
    Thank alot

    ReplyDelete
  40. You are welcome, anonymous.

    ReplyDelete
  41. It is very very useful
    Thank you.

    ReplyDelete
  42. Thanks, glad you found it useful.

    ReplyDelete
  43. Very Usefull Info Sujit... you rock... thanks for sharing

    ReplyDelete
  44. Thanks for the kind words, Rajkiran, glad it helped you.

    ReplyDelete
  45. Thank you! you save my life!!

    ReplyDelete
  46. This may be 7+ years old, but it is still a really clean, easy, and reusable solution.

    ReplyDelete
  47. You made my day. Thanks a lot.

    ReplyDelete
  48. I made a little app for this with a different approach:

    https://danjee.github.io/hedgehog/

    ReplyDelete
  49. Very cool! Don't do much Spring anymore, but looks quite useful (and more than a little app :-)).

    ReplyDelete
  50. Could you please post a license for the code?

    ReplyDelete
  51. There is no license, please use it if you find it useful.

    ReplyDelete

Comments are moderated to prevent spam.