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.
This solution is very helpful, thank you.
ReplyDeleteA.K.
You are welcome, and thanks for the feedback.
ReplyDeleteIt'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.
ReplyDeleteYou are welcome, Ely. Glad it helped.
ReplyDeleteThanks a lot for your useful post and wish all the best.
ReplyDeleteI 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
Thanks alot for your greate post. I feel very greateful.
ReplyDeleteWish you all the best:)
Hi Thabet, thanks very much for your kind words, and I am glad my post helped.
ReplyDeleteHi 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.
ReplyDeleteIf 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.
ReplyDeleteYes, 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?
ReplyDeleteDoes 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...
ReplyDeleteSpringApplicationContext 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.
ReplyDeleteThank you very much for your class code. It works fine !!!
ReplyDeleteIf you want to get a Spring context from somewhere where you can get a ServletContext, try this:
ReplyDeleteServletContext theServletContext = ...;
WebApplicationContext theWAC = WebApplicationContextUtils.getWebApplicationContext(
theServletContext);
I have been looking for this for many days now.
ReplyDeleteI 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
Thanks for the comments, krizsan and anonymous.
ReplyDelete@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.
Thanks for the article.
ReplyDeleteWhen 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 ?
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.
ReplyDeleteHi, I am using different approach. The code is like this:
ReplyDeletepublic 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.
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.
ReplyDeletehey thanx man....your code is really helpful
ReplyDeleteThanks 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.
ReplyDeleteHi Sujit,
ReplyDeleteHow would I use Yonghe's solution for multiple contexts
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 :-).
ReplyDeleteThank you Salmon, it was very helpful.
ReplyDeleteThanks Hakan, glad it helped.
ReplyDeleteExactly what I was looking for. So simple. Thanks.
ReplyDeleteThanks 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).
ReplyDeleteThank you very much too, it works for me perfectly.
ReplyDeleteThanks, you are welcome.
ReplyDeleteYou 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.
ReplyDeleteIt works! Thank you very much!
ReplyDeleteYou are welcome, glad it helped.
ReplyDeleteWow..!! Solved a long running problem in few minutes..!! Great job.!
ReplyDeleteHi, you're welcome, glad it helped you.
ReplyDeleteThis really worked for me too... I had to start a servlet and from there access spring managed beans...
ReplyDeleteHi Shiva, glad it helped you.
ReplyDeleteThanks Sujith. Simple and clean solution.
ReplyDeleteAs 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.
Thanks for the kind words, Sreekumar.
ReplyDeleteVery very useful!!
ReplyDeleteThank alot
You are welcome, anonymous.
ReplyDeleteIt is very very useful
ReplyDeleteThank you.
Thanks, glad you found it useful.
ReplyDeleteVery Usefull Info Sujit... you rock... thanks for sharing
ReplyDeleteThanks for the kind words, Rajkiran, glad it helped you.
ReplyDeleteThank you! you save my life!!
ReplyDeleteThis may be 7+ years old, but it is still a really clean, easy, and reusable solution.
ReplyDeleteThanks Anonymous :-).
ReplyDeleteYou made my day. Thanks a lot.
ReplyDeleteI made a little app for this with a different approach:
ReplyDeletehttps://danjee.github.io/hedgehog/
Very cool! Don't do much Spring anymore, but looks quite useful (and more than a little app :-)).
ReplyDeleteCould you please post a license for the code?
ReplyDeleteThere is no license, please use it if you find it useful.
ReplyDelete