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 (moderated to prevent spam):
This solution is very helpful, thank you.
A.K.
You are welcome, and thanks for the feedback.
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.
You are welcome, Ely. Glad it helped.
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
Thanks alot for your greate post. I feel very greateful.
Wish you all the best:)
Hi Thabet, thanks very much for your kind words, and I am glad my post helped.
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.
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.
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?
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...
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.
Thank you very much for your class code. It works fine !!!
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);
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
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.
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 ?
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.
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.
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.
hey thanx man....your code is really helpful
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.
Hi Sujit,
How 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 :-).
Thank you Salmon, it was very helpful.
Thanks Hakan, glad it helped.
Exactly what I was looking for. So simple. Thanks.
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).
Thank you very much too, it works for me perfectly.
Thanks, you are welcome.
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.
It works! Thank you very much!
You are welcome, glad it helped.
Wow..!! Solved a long running problem in few minutes..!! Great job.!
Hi, you're welcome, glad it helped you.
This really worked for me too... I had to start a servlet and from there access spring managed beans...
Hi Shiva, glad it helped you.
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.
Thanks for the kind words, Sreekumar.
Very very useful!!
Thank alot
You are welcome, anonymous.
It is very very useful
Thank you.
Thanks, glad you found it useful.
Very Usefull Info Sujit... you rock... thanks for sharing
Thanks for the kind words, Rajkiran, glad it helped you.
Thank you! you save my life!!
This may be 7+ years old, but it is still a really clean, easy, and reusable solution.
Thanks Anonymous :-).
You made my day. Thanks a lot.
I made a little app for this with a different approach:
https://danjee.github.io/hedgehog/
Very cool! Don't do much Spring anymore, but looks quite useful (and more than a little app :-)).
Could you please post a license for the code?
There is no license, please use it if you find it useful.
Post a Comment