Thursday, 15 March 2012

Integrating Spring Into Legacy Applications

One of the things that all Spring developers like to do is to shoehorn Spring into any application they work on - it’s one of my guilty pleasures in life: you see some code, think it’s rubbish because it contains several well known anti-patterns and then think how cool it would be if this app was a Spring app.

When working with legacy code, you can’t convert it into a fully fledged Spring app over night, that takes time. What you need to do is to add Spring code a little at a time: piece by piece and there’s one good way of doing that.

In the following scenario, you’re working on some legacy code and you’ve written a Spring bean called: MySpringBean and it needs to use the legacy class: LegacyAppClass

The legacy class looks like this:

public class LegacyAppClass {
 
// some old code goes here

 
public void legacyDoSomethingMethod() {
   
System.out.println("This is so old it doesn't use a logger....");
 
}
}

...whilst your new SpringBean looks like this:

public class MySpringBean {

 
private LegacyAppClass injectedBean;

 
@Override
 
public String toString() {
   
return "The toString()";
 
}

 
public LegacyAppClass getInjectedBean() {
   
return injectedBean;
 
}

 
public void setInjectedBean(LegacyAppClass injectedBean) {
   
this.injectedBean = injectedBean;
 
}

 
public void myDoSomethingMethod() {
   
injectedBean.legacyDoSomethingMethod();
 
}

}

...as you can see, the myDoSomethingMethod() method needs to call the legacy legacyDoSomethingMethod() method.

Given that any legacy application will have its own way of creating various objects, and that your new Spring code will need to use those objects to get its job done, then you need a way of combining the legacy objects with your shiny new ones. This will usually involve adding the legacy objects into your Spring Context and injecting them into your objects and to do this you need Spring’s StaticApplicationContext.

  @Test
 
public void loadExternalClassTest2() {

   
LegacyAppClass myInstance = new LegacyAppClass();
    GenericApplicationContext parentContext =
new StaticApplicationContext();

    parentContext.getBeanFactory
().registerSingleton("injectedBean",
        myInstance
);
    parentContext.refresh
(); // seems to be required sometimes

   
ApplicationContext context = new ClassPathXmlApplicationContext(
       
new String[] { "SpringIntegrationExample.xml" }, parentContext);

    MySpringBean mySpringBean = context.getBean
(MySpringBean.class);
    assertNotNull
(mySpringBean);

    mySpringBean.myDoSomethingMethod
();

    System.out.println
(mySpringBean.toString());
 
}

In the test code above the first point to note is that I create an instance of LegacyAppClass for use by the test, but in a real world app this will have already been created somewhere in your legacy code base. The next three lines is where the magic happens...

    GenericApplicationContext parentContext = new StaticApplicationContext();

    parentContext.getBeanFactory
().registerSingleton("injectedBean",
        myInstance
);
    parentContext.refresh
(); // seems to be required sometimes

...in the snippet above, you can see that I’m creating a StaticApplicationContext and then programmatically adding my legacy class instance to it.

    ApplicationContext context = new ClassPathXmlApplicationContext(
       
new String[] { "SpringIntegrationExample.xml" }, parentContext);

The final task, as shown above, is to then create a new Spring application context using whatever method is suitable for your project. In this case, I’ve used the proverbial ClassPathXmlApplicationContext but other types of app context work just as well.

You may say that this is a simple Micky-Mouse example, but from experience it does scale very well. It’s currently being used by a couple of full scale old style JSP Front Strategy MVC applications, (covered in detail in my blog from last October called Everybody Knows About MVC), as part of an implementation of Martin Fowler’s Strangler Pattern.

Finally, in the interests of completeness, below is the XML config for this example:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  
 <bean id="mySpringBean" class="miscillaneous.springintegration.MySpringBean">
  <property name="injectedBean" ref="injectedBean"/>
 </bean>
</beans>

8 comments:

jlemm said...

good post, thanks.
("pragmatically adding my legacy class instance to it" - you mean "programmatically", right?)

Roger Hughes said...

Thanks for the comment: typo corrected...

jlemm said...

fowler's article on the "strangler pattern" is fascinating, i feel inspired by it, as we're dealing with a similar issue.
which brings up this question: in a legacy servlet-based application, would the stuff in your article work, without having to switch it to org.springframework.web.servlet.DispatcherServlet and Spring MVC? what would web.xml look like?

jlemm said...

i realize now that what i want to do is actually the opposite of what you outline here: you compose a spring bean with a legacy object; i want to inject a spring bean into legacy code.
say i have this in a servlet's doPost method:

PrintWriter out = response.getWriter();
Book book = new Book();
book.setAuthor("David Foster Wallace");
book.setTitle("Infinite Jest");
out.println("Title is: " + book.getTitle());
out.println("Author is: " + book.getAuthor());

now i want to let spring create the book object, something like:

Book book = (Book ) context.getBean("bookBean");

where bookBean is wired in the xml file.
the trick is getting the context and so far i have not been able to get it to work in a servlet environment.

Roger Hughes said...

Jlemm,
To answer your question, it does work without switching your app to: org.springframework.web.servlet.DispatcherServlet as this kind of legacy app doesn’t use Spring MVC. I approached this problem by writing a class that implements

javax.servlet.ServletContextListener

for example: class com.MyContextListener implements ServletContextListener

Implement the contextInitialized(ServletContextEvent sce) method adding the Spring initialisation code from the ‘loadExternalClassTest2’ method above so that you create and load your Spring application context. To ensure that your Spring ApplicationContext is available throughout your app, simply be pragmatic and make it a static variable accessed via a static method, something like:

public static ApplicationContext MyContextListener.getContext();

That way, you'll be able to write code that goes something like:

Book book = (Book )MyContextListener.getContext().getBean("bookBean");

To ensure that your listener class is created add the following to your web.xml file in the appropriate place:

<listener>
    <listener-class>com.myContextListener</listener-class>
</listener>

(It generally goes between the and the elements…)

...when the server starts, it creates an instance of MyContextListener and calls contextInitialized(...)

Hope this helps. In retrospect, perhaps I should have included this in my original blog.

jlemm said...

thanks so much for the detailed reply.

I must be missing something, but it looks like i got it to work without the ContextListener.

public class MyApplicationContext {

private final static ApplicationContext context = new ClassPathXmlApplicationContext("/spring-beans.xml");

public static Object getSpringBean(String bean) {
return context.getBean(bean);
}
}

then in the serlvet i do:

Book book = (Book) MyApplicationContext.getSpringBean("bookBean");

what am i missing by doing it this way?

Roger Hughes said...

jlemm
You're not missing much by using the solution you describe above. Using a ContextListener is just the (preferred?) way to load application wide resources. It's good because it enforces the Single Responsibility Principle (SRP) removing the responsibility of creating a Spring ApplicartionContext from the Servlet into its own class. It also ensures that resources are loaded at the correct point in the web server's lifecycle. However, being pragmatic I guess that whether or not you use this approach depends upon the size of your app.

If you have a large app consisting of several servlets, loads of classes that serve hundreds of pages all of which require access to the Spring Context, then using a ContextListener would be a really good idea. If, on the other hand, you have a very small app that has only one servlet, then you could argue that the ContextListener could overly complicate things.

jlemm said...

Currently, my situation is the one you described as "a very small app that has only one servlet." that's why i thought of doing it the simplest way possible.
however, i have redesigned the thing to use the ContextListener. it makes sense, since the app is likely to grow, to implement things the "right" and scalable way from the beginning.
thanks again for your help, not only on this topic, but on the other topics you broach in your blogs and from which there is a lot to learn.