A couple of phrases have been getting mentioned lately around the tech sites: Dependency Injection (DI) and Inversion of Control(IoC) – both terms refer to the same thing. Fuzzy terms that you read when you’re looking up something else and bleep over. Start paying attention – this stuff is going to make your life so much easier, that you’ll wonder how you ever did without it.
One of the fundamental principles of good software is orthagonality – or the reduction of dependencies in a system. It’s a good practice to follow, as one of the things you get are components that can be tested easily. Better tested systems translate directly into going home early (or at least not working on weekends), happier clients and more business (or just moving on to other projects).
Dependency Injection is about having a container (don’t panic, it’s not some huge J2EE monster) provide you with components that your classes depend on at runtime by invoking methods on your classes and passing in the dependencies. Your components concentrate on doing their business, and rely upon the container to do its thing (manage transactions, inject aspect code, whatever…). I hope that by showing what this can do you’ll see the potential benefits for yourself.
Why is this important and why should you care? Your classes are plain JavaBeans (POJOs) that can be easily tested using JUnit in your IDE. Need to test a business class that relies on database access objects? No problem – mock up your DAO, and pass it to the business object in the test class’ setUp() method. The best thing is that there is no dependency on foreign APIs, no interfaces to implement – just code, configure and off you go.
Rather than doing some crappy abstract example using two beans, I’m going to make it more realistic by showing you how you would do DI in a Struts application. To this end I’m going to use a DI container, called Spring. The Spring micro-container will manage the lifecycles of all of our objects, and hook the application up (though that’s nowhere near all of the things that it can do, but you can read the documentation for that). We could use other micro-containers like PicoContainer (a 76k JAR!), and it would just be a case of rewiring the configuration – our components are completely oblivious to the existence of the container.
The test application will be typical of a standard web app – a data tier, a business tier and a web tier. I won’t go through putting in a database etc, because it just confuses the issue – and I hate reading stuff that does that, so I won’t put others through it. I’ll start from the bottom and work my way out to the web tier. I am going to use interfaces for all of our major components. It’s best practice anyway, as we can easily switch between implementations, meaning that at test time we can hook in mock objects.
What you’ll need up front:
- an understanding of Struts and web apps in general
- a servlet container
- a copy of Struts (I have used version 1.1, because I already had it lying around)
- Ant – you’ll need this to build Spring
This example is not going to actually do much, it’s just a glorified hello world – sorry to disappoint. But it will show you the plumbing you need to do to start using this in your Struts apps – AND I’ve actually checked that it works! Cut and paste out of my IDE…
The data tier,
MyDao.java
package test;
/**
* Data tier interface.
*/
public interface MyDao {
public String saySomething();
}
MyDaoImpl.java
package test;
/**
* Implementation of the data tier interface.
*/
public class MyDaoImpl implements MyDao {
public String saySomething() {
return this.getClass().getName() + ":Hello";
}
}
Now for the business tier,
MyBusinessService.java
package test;
/**
* Business tier interface.
*/
public interface MyBusinessService {
public String saySomething();
}
MyBusinessServiceImpl.java
package test;
/**
* Implementation of the business tier interface.
*/
public class MyBusinessServiceImpl implements MyBusinessService {
private MyDao dao;
public void setDao(MyDao dao) {
this.dao = dao;
}
public String saySomething() {
if (dao == null) {
throw new IllegalStateException("null dao");
}
return this.getClass().getName() + ":Hello" + "\n" + dao.saySomething();
}
}
This is where it gets interesting. Normally at this point we would have created a DAO ourselves using either the new keyword, or a factory class. Not so here… The container make sure that we get class that implements the MyDao interface by calling the setter. You can call the set method anything you like, it must just start with set – standard JavaBeans conventions. This is called setter injection – Spring also supports constructor injection, which means the container will pass in the dependencies when it instantiates the object, but I won’t cover it.
If you’re guaranteed that the setter will be called, why did I put in a null check? Can’t be too careful.
And finally, our Action class:
MyAction.java
package test;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
/**
* Spring-managed struts action.
*/
public class MyAction extends Action {
private MyBusinessService businessService;
public void setBusinessService(MyBusinessService businessService) {
this.businessService = businessService;
}
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
HttpServletResponse response) {
if (businessService == null) {
throw new IllegalStateException("null businessService");
}
String greeting = this.getClass().getName() + ":Hello" + "\n" + businessService.saySomething();
request.setAttribute("greeting", greeting);
return mapping.findForward("continue");
}
}
What’s the story here? Our Action class is managed by the micro-container in the same way as our other beans.
Now, let’s display the results of this whole thing:
test.jsp
<%
String greeting = (String) request.getAttribute("greeting");
response.getWriter().write(greeting.replaceAll("\n", "<br/>"));
%>
The interesting stuff happens in our config. This time, I’ll start on the outside.
/WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Nothing unusual here, just a standard Struts ActionServlet configuration.
/WEB-INF/struts-config.xml
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"schemas/struts-config_1_1.dtd">
<struts-config>
<action-mappings>
<action path="/test" type="org.springframework.web.struts.DelegatingActionProxy">
<forward name="continue" path="/test.jsp"/>
</action>
</action-mappings>
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml"/>
</plug-in>
</struts-config>
A couple of points of interest here:
- I stuck in the struts-config.dtd file into a shemas directory under WEB-INF.
- The action is mapped not to the MyAction class, but to a proxy provided with the Spring framework. This class will provide our Action with a reference to the business tier object.
- We load a ContextLoaderPlugIn plugin. This instantiates the Spring micro-container, which in turn parses it’s config file – applicationContext.xml.
And now the last piece of the puzzle:
/WEB-INF/applicationContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="myDao" class="test.MyDaoImpl"/>
<bean id="myBusinessService" class="test.MyBusinessServiceImpl">
<property name="dao"><ref bean="myDao"/></property>
</bean>
<!--
this is where you define your action mappings, ideally this part should be in
another spring-beans.dtd conformant config file called
<our ActionServlet's servlet-name>-servlet.xml
and is added to the contextConfigLocation property of the struts plugin
-->
<bean name="/test" class="test.MyAction" singleton="false">
<property name="businessService"><ref bean="myBusinessService"/></property>
</bean>
</beans>
We define an object reference called myDao, that references an implementation of the MyDao interface.
We then define an object reference called myBusinessService, that references an implementation of the MyBusinessService interface. On that instance, Spring will call the setDao() method, passing in the myDao object.
Finally, we create a bean reference to our Action class. The DelegatingActionProxy that was mapped to the action in struts-config.xml will find the bean that matched its path and invoke it, passing the myBusinessService object into the MyAction class. By default, objects managed by Spring are singletons, so we have to explicitly tell Spring that we don’t want that behaviour for our Action class.
In Spring, the name and id attributes have the same meaning in a bean definition, but in XML an ID cannot start with the ‘/’ character. The preferred way is to use id, but use name where you have to.
Last bits and pieces to get all of this this running:
- Set up a web application as above, and add the standard Struts jar files to the lib folder.
- Get a copy of Spring, extract everything from the zip and run the ant file in the root folder. Copy spring.jar and spring-web.jar to the web application’s lib directory.
- Configure a context in your container to run the app and fire it up.
When I fired this up by requesting /test.do, I got:
test.MyAction:Hello
test.MyBusinessServiceImpl:Hello
test.MyDaoImpl:Hello
You would think that that was all, but no. Now we’ll do something that really shows of the power of this thing.
AnotherDaoImpl.java
package test;
/**
* Alternative implementation of the data tier interface.
*/
public class AnotherDaoImpl implements MyDao {
public String saySomething() {
return getClass().getName() + ": Here's a class that says something completely different";
}
}
Now in the applicationConfig.xml file change:
<bean id=”myDao” class=”test.MyDaoImpl”/>
to
<bean id=”myDao” class=”test.AnotherDaoImpl”/>
Stop the server, redeploy and start up again. Now you should get:
test.MyAction:Hello
test.MyBusinessServiceImpl:Hello
test.AnotherDaoImpl: Here’s a class that says something completely different
Powerful, isn’t it?
Comments
3 responses to “Dependency Injection with Spring and Struts”
Good to see somebody else in Dublin is taking an interest in Spring!!
For people interested in doing J2EE , only better and faster , I would thoroughly recommend it.
Paul
http://www.firstpartners.net
Its a perfect description that i have ever seen in life.so simple to understand.
Good Job.
First, a lot of thanks for making this so simple and thou elaborative to understand.