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.

47 comments (moderated to prevent spam):

Anonymous said...

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

Sujit Pal said...

You are welcome, and thanks for the feedback.

Ely Matos said...

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.

Sujit Pal said...

You are welcome, Ely. Glad it helped.

Thabet said...

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

Thabet said...

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

Wish you all the best:)

Sujit Pal said...

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

Sujit Pal said...

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.

Anonymous said...

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.

Sujit Pal said...

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?

Anonymous said...

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

Sujit Pal said...

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.

Meurwinn said...

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

krizsan said...

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);

Anonymous said...

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

Sujit Pal said...

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.

sirisha said...

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 ?

Sujit Pal said...

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.

Yonghe said...

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.

Sujit Pal said...

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.

Bapi said...

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

Sujit Pal said...

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.

Prakash said...

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

Sujit Pal said...

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 :-).

Hakan Erdogan said...

Thank you Salmon, it was very helpful.

Sujit Pal said...

Thanks Hakan, glad it helped.

Laran Evans said...

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

Sujit Pal said...

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

Lenka said...

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

Sujit Pal said...

Thanks, you are welcome.

Daniel LĂ©lis Baggio said...

Thank you!

Sujit Pal said...

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.

Anonymous said...

It works! Thank you very much!

Sujit Pal said...

You are welcome, glad it helped.

Anonymous said...

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

Sujit Pal said...

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

Shiva said...

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

Sujit Pal said...

Hi Shiva, glad it helped you.

Sreekumar said...

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.

Sujit Pal said...

Thanks for the kind words, Sreekumar.

Anonymous said...

Very very useful!!
Thank alot

Sujit Pal said...

You are welcome, anonymous.

Anonymous said...

It is very very useful
Thank you.

Sujit Pal said...

Thanks, glad you found it useful.

Rajkiran said...

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

Sujit Pal said...

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

Unknown said...

Thank you! you save my life!!