Modified by Arulazi Dhesiaseelan for NetBeans/GlassFish
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
Only a cursory knowledge of Spring itself is assumed, and as such this tutorial is ideal if you are learning or investigating Spring. Hopefully by the time you have worked your way through the tutorial material you will see how the constituent parts of the Spring Framework, namely Inversion of Control (IoC), Aspect-Oriented Programming (AOP), and the various Spring service libraries (such as the JDBC library) all fit together in the context of a Spring MVC web application.
Spring provides several options for configuring your application. The most popular one is using XML files. This is also the traditional way that has been supported from the first release of Spring. With the introduction of Annotations in Java 5, we now have an alternate way of configuring our Spring applications. The new Spring 2.5 release introduces extensive support for using Annotations to configure a web application.
This document uses the traditional XML style for configuration.
Please note that we are not going to cover any background information or theory in this tutorial; there are plenty of books available that cover the areas in depth; whenever a new class or feature is used in the tutorial, forward pointers to the relevant section(s) of the Spring reference documentation are provided where the class or feature is covered in depth.
NetBeans IDE 6.0 Full Pack (http://www.netbeans.org) provides an excellent environment for web application development. The Ant build scripts are omitted from this discussion as they are automatically generated by the IDE. The Spring plugin module for NetBeans should be installed prior to start of this project,
You may of course use pretty much any variation or version of the above software. If you want to use IntelliJ instead of NetBeans or Jetty instead of GlassFish, then many of the tutorial steps will not translate directly to your environment but you should be able to follow along anyway.
![]() |
We will start by setting up the basic project directory structure for our application, downloading the required libraries, setting up our Ant build scripts, etc. The first step gives us a solid foundation on which to develop the application proper in parts 2, 3, and 4.
Once the basic setup is out of the way, Spring itself will be introduced, starting with the Spring Web MVC framework. We will use Spring Web MVC to display the inventoried stock, which will involve writing some simple Java classes and some JSPs. We will then move onto introducing persistent data access into our application, using Spring's Simple JDBC support.
By the time we have finished all of the steps in the tutorial, we will have an application that does basic inventory management, including listing stock and permitting the price increase of such stock.
![]() |
Enter the project name as “springapp” and select “GlassFish V2 UR1” as the Server and click Next.
![]() |
Select the framework as “Spring Framework 2.5” and enter “springapp” as the dispatcher name and click Finish.
![]() |
Find below a screen shot of what your project directory structure must look like after following the above instructions. (The screen shot shows the project directory structure inside the NetBeans IDE: you do not need to use the NetBeans IDE to complete this tutorial successfully, but using NetBeans will make it much easier to follow along.)
![]() |
'springapp/web/index.jsp':
<html> <head><title>Example :: Spring Application</title></head> <body> <h1>Example - Spring Application</h1> <p>This is my test.</p> </body> </html>
Just to have a complete web application, let's update the 'web.xml' located in 'WEB-INF' directory inside the 'web' directory with the content shown below.
'springapp/web/WEB-INF/web.xml':
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" > <welcome-file-list> <welcome-file> index.jsp </welcome-file> </welcome-file-list> </web-app>
Click the Files tab in the project to view the web directory and its contents. web.xml and index.jsp files are located in this directory.
![]() |
Right click the project and click Build. It builds the web application.
![]() |
Right click the project and click Undeploy and Deploy. It deploys the web application to GlassFish server.
![]() |
The server is already started when the application was deployed in the previous step. You can now open up a browser and navigate to the starting page of our application at the following URL: http://localhost:8080/springapp/index.jsp .
![]() |
If you have not already downloaded the Spring Framework, now is the time to do so. We are currently using the 'Spring Framework 2.5' release that can be downloaded from http://www.springframework.org/download. Unzip this file somewhere as we are going to use several files from this download later on.
This completes the setup of the environment that is necessary, and now we can start actually developing our Spring Framework MVC application.
Note: This task is handled by the Spring Netbeans Module. So, plugin users can ignore this section.
Go to the 'springapp/web/WEB-INF' directory. Modify the minimal 'web.xml' file that we updated earlier. We will define a DispatcherServlet (also known as a 'Front Controller' (Crupi et al)). It is going to control where all our requests are routed based on information we will enter at a later point. This servlet definition also has an attendant <servlet-mapping/> entry that maps to the URL patterns that we will be using. We have decided to let any URL with an '.htm' extension be routed to the 'springapp' servlet (the DispatcherServlet).
'springapp/web/WEB-INF/web.xml':
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" > <servlet> <servlet-name>springapp</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springapp</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file> index.jsp </welcome-file> </welcome-file-list> </web-app>
Next, update the file 'springapp-servlet.xml' located in the 'springapp/web/WEB-INF' directory. This file contains the bean definitions (plain old Java objects) used by the DispatcherServlet. It is the WebApplicationContext where all web-related components go. The name of this file is determined by the value of the <servlet-name/> element from the 'web.xml', with '-servlet' appended to it (hence 'springapp-servlet.xml'). This is the standard naming convention used with Spring's Web MVC framework. Now, add a bean entry named '/hello.htm' and specify the class as springapp.web.HelloController. This defines the controller that our application will be using to service a request with the corresponding URL mapping of '/hello.htm'. The Spring Web MVC framework uses an implementation class of the interface called HandlerMapping to define the mapping between a request URL and the object that is going to handle that request (the handler). Unlike the DispatcherServlet, the HelloController is responsible for handling a request for a particular page of the website and is also known as a 'Page Controller' (Fowler). The default HandlerMapping that the DispatcherServlet uses is the BeanNameUrlHandlerMapping; this class will use the bean name to map to the URL in the request so that the DispatcherServlet knows which controller must be invoked for handling different URLs.
'springapp/web/WEB-INF/springapp-servlet.xml':
<?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-2.5.xsd"> <!-- the application context definition for the springapp DispatcherServlet --> <bean name="/hello.htm" class="springapp.web.HelloController"/> </beans>
The spring libraries are copied when the project is first created. These jars will be deployed to the server and they are also used during the build process.
![]() |
Create your Controller class – we are naming it HelloController, and it is defined in the 'springapp.web' package. Right click ‘Source Packages’ and create a Java class 'HelloController.java' with the package name 'springapp.web'.
'springapp/src/java/springapp/web/HelloController.java':
package springapp.web; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.IOException; public class HelloController implements Controller { protected final Log logger = LogFactory.getLog(getClass()); public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.info("Returning hello view"); return new ModelAndView("hello.jsp"); } }
This is a very basic Controller implementation. We will be expanding this later on as well as extending some of the base controller implementations provided by Spring. In Spring Web MVC, the Controller handles the request and returns a ModelAndView - in this case, one named 'hello.jsp' which is also the name of the JSP file we will create next. The model that this class returns is actually resolved via a ViewResolver. Since we have not explicitly defined a ViewResolver, we are going to be given a default one by Spring that simply forwards to a URL matching the name of the view specified. We will modify this later on. We have also specified a logger so we can verify that we actually got into the handler.
Testing is a vital part of software development. It is also a core practice in Agile development. We have found that the best time to write tests is during development, not after, so even though our controller doesn't contain complex logic, we're going to write a test. This will allow us to make changes to it in the future with confidence. Let's use the ‘Test Packages’ for this. This is where all our tests will go in a package structure that will mirror the source tree in 'springapp/src/java'.
Create a test class called 'HelloControllerTests' and make it extend JUnit's test class TestCase. It is a unit test that verifies the view name returned by handleRequest() matches the name of the view we expect: 'hello.jsp'.
'springapp/test/springapp/web/HelloControllerTests.java':
package springapp.web; import org.springframework.web.servlet.ModelAndView; import springapp.web.HelloController; import junit.framework.TestCase; public class HelloControllerTests extends TestCase { public void testHandleRequestView() throws Exception{ HelloController controller = new HelloController(); ModelAndView modelAndView = controller.handleRequest(null, null); assertEquals("hello.jsp", modelAndView.getViewName()); } }
We can use the IDE to run the JUnit test (and all the tests we're going to write). Make sure the junit jars are present in the ‘Test Libraries’ location of the project.
Now run the JUnit by right clicking the testcase and select ‘Run File’ and the test should pass.
![]() |
Another of the best practices of Agile development is Continuous Integration. It's a good idea to ensure your tests are run with every build (ideally as automated project builds) so that you know your application logic is behaving as expected as the code evolves.
Now it is time to create our first view. As we mentioned earlier, we are forwarding to a JSP page named 'hello.jsp'. To begin with, we'll put it in the 'web' directory.
'springapp/web/hello.jsp':
<html> <head><title>Hello :: Spring Application</title></head> <body> <h1>Hello - Spring Application</h1> <p>Greetings.</p> </body> </html>
Refer section 1.3 for building and deploying the web application from an IDE.
Let's try this new version of the application.
Open a browser and browse to http://localhost:8080/springapp/hello.htm .
![]() |
Let's take quick look at the parts of our application that we have created so far.
Find below a screen shot of what your project directory structure must look like after following the above instructions.
![]() |
This is Part 2 of a step-by-step tutorial on how to develop a web application from scratch using the Spring Framework. In Part 1 we configured the environment and set up a basic application that we will now flesh out.
This is what we have implemented so far:
We will be using the JSP Standard Tag Library (JSTL), so let's make sure jstl.jar and standard.jar exist in 'Libraries' directory.
We will be creating a 'header' file that will be included in every JSP page that we're going to write. We ensure the same definitions are included in all our JSPs simply by including the header file. We're also going to put all JSPs in a directory named 'jsp' under the 'WEB-INF' directory. This will ensure that views can only be accessed via the controller since it will not be possible to access these pages directly via a URL. This strategy might not work in some application servers and if this is the case with the one you are using, move the 'jsp' directory up a level. You would then use 'springapp/web/jsp' as the directory instead of 'springapp/web/WEB-INF/jsp' in all the code examples that will follow.
First we create the header file for inclusion in all the JSPs we create.
'springapp/web/WEB-INF/jsp/include.jsp':
<%@ page session="false"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
Now we can update 'index.jsp' to use this include file and since we are using JSTL, we can use the <c:redirect/> tag for redirecting to our front Controller. This means all requests for 'index.jsp' will go through our application framework. Just delete the current contents of 'index.jsp' and replace it with the following:
'springapp/web/index.jsp':
<%@ include file="/WEB-INF/jsp/include.jsp" %> <%-- Redirected because we can't set the welcome page to a virtual URL. --%> <c:redirect url="/hello.htm"/>
Move 'hello.jsp' to the 'WEB-INF/jsp' directory. Add the same include directive we added to 'index.jsp' to 'hello.jsp'. We also add the current date and time as output to be retrieved from the model passed to the view which will be rendered using the JSTL <c:out/> tag.
'springapp/web/WEB-INF/jsp/hello.jsp':
<%@ include file="/WEB-INF/jsp/include.jsp" %> <html> <head><title>Hello :: Spring Application</title></head> <body> <h1>Hello - Spring Application</h1> <p>Greetings, it is now <c:out value="${now}"/></p> </body> </html>
Before we update the location of the JSP in our controller, let's update our unit test class first. We know we need to update the view's resource reference with its new location 'WEB-INF/jsp/hello.jsp'. We also know there should be an object in the model mapped to the key "now".
'springapp/tests/HelloControllerTests.java':
package springapp.web; import org.springframework.web.servlet.ModelAndView; import springapp.web.HelloController; import junit.framework.TestCase; public class HelloControllerTests extends TestCase { public void testHandleRequestView() throws Exception{ HelloController controller = new HelloController(); ModelAndView modelAndView = controller.handleRequest(null, null); assertEquals("WEB-INF/jsp/hello.jsp", modelAndView.getViewName()); assertNotNull(modelAndView.getModel()); String nowValue = (String) modelAndView.getModel().get("now"); assertNotNull(nowValue); } }
Next, we run the JUnit by right clicking the testcase and select ‘Run File’ and the test should fail.
![]() |
Now we update HelloController by setting the view's resource reference to its new location 'WEB-INF/jsp/hello.jsp' as well as set the key/value pair for the current date and time value in the model with the key identifier: "now" and the string value: 'now'.
'springapp/src/java/springapp/web/HelloController.java':
package springapp.web; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.util.Date; public class HelloController implements Controller { protected final Log logger = LogFactory.getLog(getClass()); public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String now = (new Date()).toString(); logger.info("Returning hello view with " + now); return new ModelAndView("WEB-INF/jsp/hello.jsp", "now", now); } }
We rerun our JUnit and the test passes.
Remember that the Controller has already been configured in 'springapp-servlet.xml' file, so we are ready to try out our enhancements after we build and deploy this new code. When we enter http://localhost:8080/springapp/ in a browser, it should pull up the welcome file 'index.jsp', which should redirect to 'hello.htm' and is handled by the DispatcherServlet, which in turn delegates our request to the page controller that puts the date and time in the model and then makes the model available to the view 'hello.jsp'.
![]() |
Right now the controller specifies the full path of the view, which creates an unnecessary dependency between the controller and the view. Ideally we would like to map to the view using a logical name, allowing us to switch the view without having to change the controller. You can set this mapping in a properties file if you like using a ResourceBundleViewResolver and a SimpleUrlHandlerMapping class. For the basic mapping of a view to a location, simply set a prefix and a suffix on the InternalResourceViewResolver. This second approach is the one that we will implement now, so we modify the 'springapp-servlet.xml' and declare a 'viewResolver' entry. By choosing the JstlView, it will enable us to use JSTL in combination with message resource bundles as well as provide us with the support for internationalization.
'springapp/web/WEB-INF/springapp-servlet.xml':
<?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-2.0.xsd"> <!-- the application context definition for the springapp DispatcherServlet --> <bean name="/hello.htm" class="springapp.web.HelloController"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean> </beans>
We update the view name in the controller test class HelloControllerTests to 'hello' and rerun the test to check it fails.
'springapp/test/springapp/web/HelloControllerTests.java':
package springapp.web; import org.springframework.web.servlet.ModelAndView; import springapp.web.HelloController; import junit.framework.TestCase; public class HelloControllerTests extends TestCase { public void testHandleRequestView() throws Exception{ HelloController controller = new HelloController(); ModelAndView modelAndView = controller.handleRequest(null, null); assertEquals("hello", modelAndView.getViewName()); assertNotNull(modelAndView.getModel()); String nowValue = (String) modelAndView.getModel().get("now"); assertNotNull(nowValue); } }
We then remove the prefix and suffix from the view name in the controller, leaving the controller to reference the view by its logical name "hello".
'springapp/src/java/springapp/web/HelloController.java':
package springapp.web; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.util.Date; public class HelloController implements Controller { protected final Log logger = LogFactory.getLog(getClass()); public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String now = (new Date()).toString(); logger.info("Returning hello view with " + now); return new ModelAndView("hello", "now", now); } }
Rerun the test and it should now pass.
Let's compile and deploy the application and verify the application still works.
Let's take quick look at what we have created in Part 2.
These are the existing artifacts we have changed in Part 2.
Find below a screen shot of what your project directory structure must look like after following the above instructions.
![]() |
This is Part 3 of a step-by-step tutorial on how to develop a Spring application. In this section, we will adopt a pragmatic Test-Driven Development (TDD) approach for creating the domain objects and implementing the business logic for our inventory management system. This means we'll "code a little, test a little, code some more then test some more". In Part 1 we configured the environment and set up a basic application. In Part 2 we refined the application by decoupling the view from the controller.
Spring is about making simple things easy and the hard things possible. The fundamental construct that makes this possible is Spring's use of Plain Old Java Objects (POJOs). POJOs are essentially plain old Java classes free from any contract usually enforced by a framework or component architecture through subclassing or the implementation of interfaces. POJOs are plain old objects that are free from such constraints, making object-oriented programming possible once again. When you are working with Spring, the domain objects and services you implement will be POJOs. In fact, almost everything you implement should be a POJO. If it's not, you should be sure to ask yourself why that is. In this section, we will begin to see the simplicity and power of Spring.
Find below a class diagram of our inventory management system.
![]() |
First we implement the Product class as a POJO with a default constructor (automatically provided if we don't specify any constructors) and getters and setters for its properties 'description' and 'price'. Let's also make it Serializable, not necessary for our application, but could come in handy later on when we persist and store its state. The class is a domain object, so it belongs in the 'domain' package.
'springapp/src/java/springapp/domain/Product.java':
package springapp.domain; import java.io.Serializable; public class Product implements Serializable { private String description; private Double price; public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("Description: " + description + ";"); buffer.append("Price: " + price); return buffer.toString(); } }
'springapp/test/springapp/domain/ProductTests.java':
package springapp.domain; import junit.framework.TestCase; public class ProductTests extends TestCase { private Product product; protected void setUp() throws Exception { product = new Product(); } public void testSetAndGetDescription() { String testDescription = "aDescription"; assertNull(product.getDescription()); product.setDescription(testDescription); assertEquals(testDescription, product.getDescription()); } public void testSetAndGetPrice() { double testPrice = 100.00; assertEquals(0, 0, 0); product.setPrice(testPrice); assertEquals(testPrice, product.getPrice(), 0); } }
'springapp/src/java/springapp/service/ProductManager.java':
package springapp.service; import java.io.Serializable; import java.util.List; import springapp.domain.Product; public interface ProductManager extends Serializable{ public void increasePrice(int percentage); public List<Product> getProducts(); }
Let's create the SimpleProductManager class that implements the ProductManager interface.
'springapp/src/java/springapp/service/SimpleProductManager.java':
package springapp.service; import java.util.List; import springapp.domain.Product; public class SimpleProductManager implements ProductManager { public List<Product> getProducts() { throw new UnsupportedOperationException(); } public void increasePrice(int percentage) { throw new UnsupportedOperationException(); } public void setProducts(List<Product> products) { throw new UnsupportedOperationException(); } }
Before we implement the methods in SimpleProductManager, we're going to define some tests first. The strictest definition of Test Driven Development (TDD) is to always write the tests first, then the code. A looser interpretation of it is more akin to Test Oriented Development (TOD), where we alternate between writing code and tests as part of the development process. The most important thing is for a codebase to have as complete a set of unit tests as possible, so how you achieve it becomes somewhat academic. Most TDD developers, however, do agree that the quality of tests is always higher when they are written at around the same time as the code that is being developed, so that's the approach we're going to take.
To write effective tests, you have to consider all the possible pre- and post-conditions of a method being tested as well as what happens within the method. Let's start by testing a call to getProducts() returns null.
'springapp/test/springapp/service/SimpleProductManagerTests.java':
package springapp.service; import junit.framework.TestCase; public class SimpleProductManagerTests extends TestCase { private SimpleProductManager productManager; protected void setUp() throws Exception { productManager = new SimpleProductManager(); } public void testGetProductsWithNoProducts() { productManager = new SimpleProductManager(); assertNull(productManager.getProducts()); } }
Next we implement a test for retrieving a list of stub products populated with test data. We know that we'll need to populate the products list in the majority of our test methods in SimpleProductManagerTests, so we define the stub list in JUnit's setUp(), a method that is invoked before each test method is called.
'springapp/test/springapp/service/SimpleProductManagerTests.java':
package springapp.service; import java.util.ArrayList; import java.util.List; import springapp.domain.Product; import junit.framework.TestCase; public class SimpleProductManagerTests extends TestCase { private SimpleProductManager productManager; private List<Product> products; private static int PRODUCT_COUNT = 2; private static Double CHAIR_PRICE = new Double(20.50); private static String CHAIR_DESCRIPTION = "Chair"; private static String TABLE_DESCRIPTION = "Table"; private static Double TABLE_PRICE = new Double(150.10); protected void setUp() throws Exception { productManager = new SimpleProductManager(); products = new ArrayList<Product>(); // stub up a list of products Product product = new Product(); product.setDescription("Chair"); product.setPrice(CHAIR_PRICE); products.add(product); product = new Product(); product.setDescription("Table"); product.setPrice(TABLE_PRICE); products.add(product); productManager.setProducts(products); } public void testGetProductsWithNoProducts() { productManager = new SimpleProductManager(); assertNull(productManager.getProducts()); } public void testGetProducts() { List<Product> products = productManager.getProducts(); assertNotNull(products); assertEquals(PRODUCT_COUNT, productManager.getProducts().size()); Product product = products.get(0); assertEquals(CHAIR_DESCRIPTION, product.getDescription()); assertEquals(CHAIR_PRICE, product.getPrice()); product = products.get(1); assertEquals(TABLE_DESCRIPTION, product.getDescription()); assertEquals(TABLE_PRICE, product.getPrice()); } }
Rerun all the Junit tests and our two tests should fail.
We go back to the SimpleProductManager and implement the getter and setter methods for the products property.
'springapp/src/java/springapp/service/SimpleProductManager.java':
package springapp.service; import java.util.ArrayList; import java.util.List; import springapp.domain.Product; public class SimpleProductManager implements ProductManager { private List<Product> products; public List<Product> getProducts() { return products; } public void increasePrice(int percentage) { // TODO Auto-generated method stub } public void setProducts(List<Product> products) { this.products = products; } }
Rerun the Junit tests and all our tests should pass.
We proceed by implementing the following tests for the increasePrice() method:
'springapp/test/springapp/service/SimpleProductManagerTests.java':
package springapp.service; import java.util.ArrayList; import java.util.List; import springapp.domain.Product; import junit.framework.TestCase; public class SimpleProductManagerTests extends TestCase { private SimpleProductManager productManager; private List<Product> products; private static int PRODUCT_COUNT = 2; private static Double CHAIR_PRICE = new Double(20.50); private static String CHAIR_DESCRIPTION = "Chair"; private static String TABLE_DESCRIPTION = "Table"; private static Double TABLE_PRICE = new Double(150.10); private static int POSITIVE_PRICE_INCREASE = 10; protected void setUp() throws Exception { productManager = new SimpleProductManager(); products = new ArrayList<Product>(); // stub up a list of products Product product = new Product(); product.setDescription("Chair"); product.setPrice(CHAIR_PRICE); products.add(product); product = new Product(); product.setDescription("Table"); product.setPrice(TABLE_PRICE); products.add(product); productManager.setProducts(products); } public void testGetProductsWithNoProducts() { productManager = new SimpleProductManager(); assertNull(productManager.getProducts()); } public void testGetProducts() { List<Product> products = productManager.getProducts(); assertNotNull(products); assertEquals(PRODUCT_COUNT, productManager.getProducts().size()); Product product = products.get(0); assertEquals(CHAIR_DESCRIPTION, product.getDescription()); assertEquals(CHAIR_PRICE, product.getPrice()); product = products.get(1); assertEquals(TABLE_DESCRIPTION, product.getDescription()); assertEquals(TABLE_PRICE, product.getPrice()); } public void testIncreasePriceWithNullListOfProducts() { try { productManager = new SimpleProductManager(); productManager.increasePrice(POSITIVE_PRICE_INCREASE); } catch(NullPointerException ex) { fail("Products list is null."); } } public void testIncreasePriceWithEmptyListOfProducts() { try { productManager = new SimpleProductManager(); productManager.setProducts(new ArrayList<Product>()); productManager.increasePrice(POSITIVE_PRICE_INCREASE); } catch(Exception ex) { fail("Products list is empty."); } } public void testIncreasePriceWithPositivePercentage() { productManager.increasePrice(POSITIVE_PRICE_INCREASE); double expectedChairPriceWithIncrease = 22.55; double expectedTablePriceWithIncrease = 165.11; List<Product> products = productManager.getProducts(); Product product = products.get(0); assertEquals(expectedChairPriceWithIncrease, product.getPrice()); product = products.get(1); assertEquals(expectedTablePriceWithIncrease, product.getPrice()); } }
We return to SimpleProductManager to implement increasePrice().
'springapp/src/java/springapp/service/SimpleProductManager.java':
package springapp.service; import java.util.List; import springapp.domain.Product; public class SimpleProductManager implements ProductManager { private List<Product> products; public List<Product> getProducts() { return products; } public void increasePrice(int percentage) { if (products != null) { for (Product product : products) { double newPrice = product.getPrice().doubleValue() * (100 + percentage)/100; product.setPrice(newPrice); } } } public void setProducts(List<Product> products) { this.products = products; } }
Rerun the JUnit tests and all our tests should pass. *HURRAH* JUnit has a saying: “keep the bar green to keep the code clean.” For those of you running the tests in an IDE and are new to unit testing, we hope you're feeling imbued with a sense of greater sense of confidence and certainty that the code is truly working as specified in the business rules specification and as you intend. We certainly do.
We're now ready to move back into the web layer to put a list of products into our Controller model.
Find below a screen shot of what your project directory structure must look like after following the above instructions.
![]() |
'springapp/src/java/springapp/web/InventoryController.java':
package springapp.web; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; import java.util.HashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import springapp.service.ProductManager; public class InventoryController implements Controller { protected final Log logger = LogFactory.getLog(getClass()); private ProductManager productManager; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String now = (new java.util.Date()).toString(); logger.info("returning hello view with " + now); Map<String, Object> myModel = new HashMap<String, Object>(); myModel.put("now", now); myModel.put("products", this.productManager.getProducts()); return new ModelAndView("hello", "model", myModel); } public void setProductManager(ProductManager productManager) { this.productManager = productManager; } }
We will also need to modify the InventoryControllerTests to supply a ProductManager and extract the value for 'now' from the model Map before the tests will pass again.
'springapp/test/web/InventoryControllerTests.java':
package springapp.web; import java.util.Map; import org.springframework.web.servlet.ModelAndView; import springapp.service.SimpleProductManager; import springapp.web.InventoryController; import junit.framework.TestCase; public class InventoryControllerTests extends TestCase { public void testHandleRequestView() throws Exception{ InventoryController controller = new InventoryController(); controller.setProductManager(new SimpleProductManager()); ModelAndView modelAndView = controller.handleRequest(null, null); assertEquals("hello", modelAndView.getViewName()); assertNotNull(modelAndView.getModel()); Map modelMap = (Map) modelAndView.getModel().get("model"); String nowValue = (String) modelMap.get("now"); assertNotNull(nowValue); } }
'springapp/web/WEB-INF/jsp/hello.jsp':
<%@ include file="/WEB-INF/jsp/include.jsp" %> <html> <head><title><fmt:message key="title"/></title></head> <body> <h1><fmt:message key="heading"/></h1> <p><fmt:message key="greeting"/> <c:out value="${model.now}"/></p> <h3>Products</h3> <c:forEach items="${model.products}" var="prod"> <c:out value="${prod.description}"/> <i>$<c:out value="${prod.price}"/></i><br><br> </c:forEach> </body> </html>
'springapp/web/WEB-INF/springapp-servlet.xml':
<?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-2.5.xsd"> <!-- the application context definition for the springapp DispatcherServlet --> <bean id="productManager" class="springapp.service.SimpleProductManager"> <property name="products"> <list> <ref bean="product1"/> <ref bean="product2"/> <ref bean="product3"/> </list> </property> </bean> <bean id="product1" class="springapp.domain.Product"> <property name="description" value="Lamp"/> <property name="price" value="5.75"/> </bean> <bean id="product2" class="springapp.domain.Product"> <property name="description" value="Table"/> <property name="price" value="75.25"/> </bean> <bean id="product3" class="springapp.domain.Product"> <property name="description" value="Chair"/> <property name="price" value="22.79"/> </bean> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="messages"/> </bean> <bean name="/hello.htm" class="springapp.web.InventoryController"> <property name="productManager" ref="productManager"/> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
'springapp/web/WEB-INF/classes/messages.properties':
title=SpringApp heading=Hello :: SpringApp greeting=Greetings, it is now
Now re-build the application and deploy it. Let's try accessing this new version of the application and you should see the following:
![]() |
'springapp/web/WEB-INF/web.xml':
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" > <servlet> <servlet-name>springapp</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springapp</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file> index.jsp </welcome-file> </welcome-file-list> <jsp-config> <taglib> <taglib-uri>/spring</taglib-uri> <taglib-location>/WEB-INF/tld/spring-form.tld</taglib-location> </taglib> </jsp-config> </web-app>
We also have to declare this taglib in a page directive in the jsp file, and then start using the tags we have thus imported. Add the JSP page 'priceincrease.jsp' to the 'web/WEB-INF/jsp' directory.
'springapp/web/WEB-INF/jsp/priceincrease.jsp':
<%@ include file="/WEB-INF/jsp/include.jsp" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <html> <head> <title><fmt:message key="title"/></title> <style> .error { color: red; } </style> </head> <body> <h1><fmt:message key="priceincrease.heading"/></h1> <form:form method="post" commandName="priceIncrease"> <table width="95%" bgcolor="f8f8ff" border="0" cellspacing="0" cellpadding="5"> <tr> <td align="right" width="20%">Increase (%):</td> <td width="20%"> <form:input path="percentage"/> </td> <td width="60%"> <form:errors path="percentage" cssClass="error"/> </td> </tr> </table> <br> <input type="submit" align="center" value="Execute"> </form:form> <a href="<c:url value="hello.htm"/>">Home</a> </body> </html>
'springapp/src/java/springapp/service/PriceIncrease.java':
package springapp.service; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class PriceIncrease { /** Logger for this class and subclasses */ protected final Log logger = LogFactory.getLog(getClass()); private int percentage; public void setPercentage(int i) { percentage = i; logger.info("Percentage set to " + i); } public int getPercentage() { return percentage; } }
The following validator class gets control after the user presses submit. The values entered in the form will be set on the command object by the framework. The validate(..) method is called and the command object (PriceIncrease) and a contextual object to hold any errors are passed in.
'springapp/src/java/springapp/service/PriceIncreaseValidator.java':
package springapp.service; import org.springframework.validation.Validator; import org.springframework.validation.Errors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class PriceIncreaseValidator implements Validator { private int DEFAULT_MIN_PERCENTAGE = 0; private int DEFAULT_MAX_PERCENTAGE = 50; private int minPercentage = DEFAULT_MIN_PERCENTAGE; private int maxPercentage = DEFAULT_MAX_PERCENTAGE; /** Logger for this class and subclasses */ protected final Log logger = LogFactory.getLog(getClass()); public boolean supports(Class clazz) { return PriceIncrease.class.equals(clazz); } public void validate(Object obj, Errors errors) { PriceIncrease pi = (PriceIncrease) obj; if (pi == null) { errors.rejectValue("percentage", "error.not-specified", null, "Value required."); } else { logger.info("Validating with " + pi + ": " + pi.getPercentage()); if (pi.getPercentage() > maxPercentage) { errors.rejectValue("percentage", "error.too-high", new Object[] {new Integer(maxPercentage)}, "Value too high."); } if (pi.getPercentage() <= minPercentage) { errors.rejectValue("percentage", "error.too-low", new Object[] {new Integer(minPercentage)}, "Value too low."); } } } public void setMinPercentage(int i) { minPercentage = i; } public int getMinPercentage() { return minPercentage; } public void setMaxPercentage(int i) { maxPercentage = i; } public int getMaxPercentage() { return maxPercentage; } }
'springapp/web/WEB-INF/springapp-servlet.xml':
<?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-2.5.xsd"> <!-- the application context definition for the springapp DispatcherServlet --> <bean id="productManager" class="springapp.service.SimpleProductManager"> <property name="products"> <list> <ref bean="product1"/> <ref bean="product2"/> <ref bean="product3"/> </list> </property> </bean> <bean id="product1" class="springapp.domain.Product"> <property name="description" value="Lamp"/> <property name="price" value="5.75"/> </bean> <bean id="product2" class="springapp.domain.Product"> <property name="description" value="Table"/> <property name="price" value="75.25"/> </bean> <bean id="product3" class="springapp.domain.Product"> <property name="description" value="Chair"/> <property name="price" value="22.79"/> </bean> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="messages"/> </bean> <bean name="/hello.htm" class="springapp.web.InventoryController"> <property name="productManager" ref="productManager"/> </bean> <bean name="/priceincrease.htm" class="springapp.web.PriceIncreaseFormController"> <property name="sessionForm" value="true"/> <property name="commandName" value="priceIncrease"/> <property name="commandClass" value="springapp.service.PriceIncrease"/> <property name="validator"> <bean class="springapp.service.PriceIncreaseValidator"/> </property> <property name="formView" value="priceincrease"/> <property name="successView" value="hello.htm"/> <property name="productManager" ref="productManager"/> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
Next, let's take a look at the controller for this form. The onSubmit(..) method gets control and does some logging before it calls the increasePrice(..) method on the ProductManager object. It then returns a ModelAndView passing in a new instance of a RedirectView created using the URL for the success view.
'springapp/src/java/springapp/web/PriceIncreaseFormController.java':
package springapp.web; import org.springframework.web.servlet.mvc.SimpleFormController; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.RedirectView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import springapp.service.ProductManager; import springapp.service.PriceIncrease; public class PriceIncreaseFormController extends SimpleFormController { /** Logger for this class and subclasses */ protected final Log logger = LogFactory.getLog(getClass()); private ProductManager productManager; public ModelAndView onSubmit(Object command) throws ServletException { int increase = ((PriceIncrease) command).getPercentage(); logger.info("Increasing prices by " + increase + "%."); productManager.increasePrice(increase); logger.info("returning from PriceIncreaseForm view to " + getSuccessView()); return new ModelAndView(new RedirectView(getSuccessView())); } protected Object formBackingObject(HttpServletRequest request) throws ServletException { PriceIncrease priceIncrease = new PriceIncrease(); priceIncrease.setPercentage(20); return priceIncrease; } public void setProductManager(ProductManager productManager) { this.productManager = productManager; } public ProductManager getProductManager() { return productManager; } }
We are also adding some messages to the 'messages.properties' resource file.
'springapp/web/WEB-INF/classes/messages.properties':
title=SpringApp heading=Hello :: SpringApp greeting=Greetings, it is now priceincrease.heading=Price Increase :: SpringApp error.not-specified=Percentage not specified!!! error.too-low=You have to specify a percentage higher than {0}! error.too-high=Don''t be greedy - you can''t raise prices by more than {0}%! required=Entry required. typeMismatch=Invalid data. typeMismatch.percentage=That is not a number!!!
Compile and deploy all this and after reloading the application we can test it. This is what the form looks like with errors displayed.
Finally, we will add a link to the price increase page from the 'hello.jsp'.
<%@ include file="/WEB-INF/jsp/include.jsp" %> <html> <head><title><fmt:message key="title"/></title></head> <body> <h1><fmt:message key="heading"/></h1> <p><fmt:message key="greeting"/> <c:out value="${model.now}"/></p> <h3>Products</h3> <c:forEach items="${model.products}" var="prod"> <c:out value="${prod.description}"/> <i>$<c:out value="${prod.price}"/></i><br><br> </c:forEach> <br> <a href="<c:url value="priceincrease.htm"/>">Increase Prices</a> <br> </body> </html>
Now, re-deploy and try the new price increase feature.
![]() |
4.7. Summary Let's look at what we did in Part 4.
Find below a screen shot of what your project directory structure must look like after following the above instructions.
![]() |
We need a script or batch file to start the database. Create a 'db' directory under the main 'springapp' directory. This new directory will contain the database files. Now, let's add a startup script:
For Linux/Mac OS X add:
'springapp/db/server.sh':
java -classpath ../web/WEB-INF/lib/hsqldb.jar org.hsqldb.Server -database test
Don't forget to change the execute permission by running 'chmod +x server.sh'.
For Windows add:
'springapp/db/server.bat':
java -classpath ..\web\WEB-INF\lib\hsqldb.jar org.hsqldb.Server -database test
Now you can open a command window, change to the 'springapp/db' directory and start the database by running one of these startup scripts.
'springapp/db/createproducts.sql':
CREATE TABLE products (
id INTEGER NOT NULL PRIMARY KEY,
description varchar(255),
price decimal(15,2)
);
CREATE INDEX products_description ON products(description);
Now we need to add our test data. Create the file 'load_data.sql' in the db directory.
'springapp/db/loaddata.sql':
INSERT INTO products (id, description, price) values(1, 'Lamp', 5.78); INSERT INTO products (id, description, price) values(2, 'Table', 75.29); INSERT INTO products (id, description, price) values(3, 'Chair', 22.81);
In the following section we will see how we can run these SQL scripts from the IDE.
![]() |
Next we execute the create table and load test data scripts using the Run SQL option from the IDE.
![]() |
Select the data from the table using the SQL command window.
![]() |
Now you can execute createTable and loadData to prepare the test data we will use later.
'springapp/src/java/springapp/repository/ProductDao.java':
package springapp.repository; import java.util.List; import springapp.domain.Product; public interface ProductDao { public List<Product> getProductList(); public void saveProduct(Product prod); }
We'll follow this with a class called JdbcProductDao that will be the JDBC implementation of this interface. Spring provides a JDBC abstraction framework that we will make use of. The biggest difference between using straight JDBC and Spring's JDBC framework is that you don't have to worry about opening and closing the connection or any statements. It is all handled for you. Another advantage is that you won't have to catch any exceptions, unless you want to. Spring wraps all SQLExceptions in it's own unchecked exception hierarchy inheriting from DataAccessException. If you want to, you can catch this exception, but since most database exceptions are impossible to recover from anyway, you might as well just let the exception propagate up to a higher level. The class SimpleJdbcDaoSupport provides convenient access to an already configured SimpleJdbcTemplate, so we extend this class. All we will have to provide in the application context is a configured DataSource.
'springapp/src/java/springapp/repository/JdbcProductDao.java':
package springapp.repository; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.simple.ParameterizedRowMapper; import org.springframework.jdbc.core.simple.SimpleJdbcDaoSupport; import springapp.domain.Product; public class JdbcProductDao extends SimpleJdbcDaoSupport implements ProductDao { /** Logger for this class and subclasses */ protected final Log logger = LogFactory.getLog(getClass()); public List<Product> getProductList() { logger.info("Getting products!"); List<Product> products = getSimpleJdbcTemplate().query( "select id, description, price from products", new ProductMapper()); return products; } public void saveProduct(Product prod) { logger.info("Saving product: " + prod.getDescription()); int count = getSimpleJdbcTemplate().update( "update products set description = :description, price = :price where id = :id", new MapSqlParameterSource().addValue("description", prod.getDescription()) .addValue("price", prod.getPrice()) .addValue("id", prod.getId())); logger.info("Rows affected: " + count); } private static class ProductMapper implements ParameterizedRowMapper<Product> { public Product mapRow(ResultSet rs, int rowNum) throws SQLException { Product prod = new Product(); prod.setId(rs.getInt("id")); prod.setDescription(rs.getString("description")); prod.setPrice(new Double(rs.getDouble("price"))); return prod; } } }
Let's go over the two DAO methods in this class. Since we are extending SimpleJdbcSupport we get a SimpleJdbcTemplate prepared and ready to use. This is accessed by calling the getSimpleJdbcTemplate() method.
The first method, getProductList() executes a query using the SimpleJdbcTemplate. We simply provide the SQL statement and a class that can handle the mapping between the ResultSet and the Product class. In our case the row mapper is a class named ProductMapper that we define as an inner class of the DAO. This class will so far not be used outside of the DAO so making it an inner class works well.
The ProductMapper implements the ParameterizedRowMapper interface that defines a single method named mapRow that must be implemented. This method will map the data from each row into a class that represents the entity you are retrieving in your query. Since the RowMapper is parameterized, the mapRow method returns the actual type that is created.
The second method saveProduct is also using the SimpleJdbcTemplate. This time we are calling the update method passing in an SQL statement together with the parameter values in the form of a MapSqlParameterSource. Using a MapSqlParameterSource allows us to use named parameters instead of the typical "?" place holders that you are used to from writing plain JDBC. The named parameters makes your code more explicit and you avoid problems caused by parameters being set out of order etc. The update method returns the count of rows affected.
We need to store the value of the primary key for each product in the Product class. This key will be used when we persist any changes to the object back to the database. To hold this key we add a private field named 'id' complete with setters and getters to Product.java.
'springapp/src/java/springapp/domain/Product.java':
package springapp.domain; import java.io.Serializable; public class Product implements Serializable { private int id; private String description; private Double price; public void setId(int i) { id = i; } public int getId() { return id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("Description: " + description + ";"); buffer.append("Price: " + price); return buffer.toString(); } }
This completes the Simple JDBC implementation of our persistence layer.
Now we can create our test class. By extending AbstractTransactionalDataSourceSpringContextTests we get a lot of nice features for free. We get dependency injection of any public setter from an application context. This application context is loaded by the test framework. All we need to do is specify the name for it in the getConfigLocations method. We also get an opportunity to prepare our database with the appropriate test data in the onSetUpInTransaction method. This is important, since we don't know the state of the database when we run our tests. As long as the table exists we will clear it and load what we need for our tests. Since we are extending a "Transactional" test, any changes we make will be automatically rolled back once the test finishes. The deleteFromTables and executeSqlScript methods are defined in the super class, so we don't have to implement them for each test. Just pass in the table names to be cleared and the name of the script that contains the test data.
'springapp/test/springapp/repository/JdbcProductDaoTests.java':
package springapp.repository; import java.util.List; import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; import springapp.domain.Product; public class JdbcProductDaoTests extends AbstractTransactionalDataSourceSpringContextTests { private ProductDao productDao; public void setProductDao(ProductDao productDao) { this.productDao = productDao; } @Override protected String[] getConfigLocations() { return new String[] {"classpath:test-context.xml"}; } @Override protected void onSetUpInTransaction() throws Exception { super.deleteFromTables(new String[] {"products"}); super.executeSqlScript("file:db/load_data.sql", true); } public void testGetProductList() { List<Product> products = productDao.getProductList(); assertEquals("wrong number of products?", 3, products.size()); } public void testSaveProduct() { List<Product> products = productDao.getProductList(); for (Product p : products) { p.setPrice(200.12); productDao.saveProduct(p); } List<Product> updatedProducts = productDao.getProductList(); for (Product p : updatedProducts) { assertEquals("wrong price of product?", 200.12, p.getPrice()); } } }
We don't have the application context file that is loaded for this test yet, so let's create this file in the 'springapp/test' directory:
'springapp/test/test-context.xml':
<?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-2.5.xsd"> <!-- the test application context definition for the jdbc based tests --> <bean id="productDao" class="springapp.repository.JdbcProductDao"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
We have defined a productDao which is the class we are testing. We have also defined a DataSource with place holders for the configuration values. These values are provided via a separate property file and at runtime, the PropertyPlaceholderConfigurer that we have defined will read this property file and substitute the place holders with the actual values. This is convenient since this isolates the connection values into their own file. These values often need to be changed during application deployment. We put this new file in the 'web/WEB-INF/classes' directory so it will be available when we run the application and also later when we deploy the web application. The content of this property file is:
'springapp/web/WEB-INF/classes/jdbc.properties':
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://localhost jdbc.username=sa jdbc.password=
We should now have enough for our tests to run and pass but it's a good practice to separate any integration tests that depend on a live database from the rest of the tests.
Time to run this Junit by right clicking the testcase and select ‘Run File’ to see if the tests pass.
Below is a screen shot of what your project directory structure should look like after following the above instructions.
![]() |
'springapp/src/java/springapp/service/SimpleProductManager.java':
package springapp.service; import java.util.List; import springapp.domain.Product; import springapp.repository.ProductDao; public class SimpleProductManager implements ProductManager { // private List<Product> products; private ProductDao productDao; public List<Product> getProducts() { // return products; return productDao.getProductList(); } public void increasePrice(int percentage) { List<Product> products = productDao.getProductList(); if (products != null) { for (Product product : products) { double newPrice = product.getPrice().doubleValue() * (100 + percentage)/100; product.setPrice(newPrice); productDao.saveProduct(product); } } } public void setProductDao(ProductDao productDao) { this.productDao = productDao; } // public void setProducts(List<Product> products) { // this.products = products; // } }
'springapp/test/springapp/repository/InMemoryProductDao.java':
package springapp.repository; import java.util.List; import springapp.domain.Product; public class InMemoryProductDao implements ProductDao { private List<Product> productList; public InMemoryProductDao(List<Product> productList) { this.productList = productList; } public List<Product> getProductList() { return productList; } public void saveProduct(Product prod) { } }
And here is the modified SimpleProductManagerTests:
'springapp/test/springapp/service/SimpleProductManagerTests.java':
package springapp.service; import java.util.ArrayList; import java.util.List; import springapp.domain.Product; import springapp.repository.InMemoryProductDao; import springapp.repository.ProductDao; import junit.framework.TestCase; public class SimpleProductManagerTests extends TestCase { private SimpleProductManager productManager; private List<Product> products; private static int PRODUCT_COUNT = 2; private static Double CHAIR_PRICE = new Double(20.50); private static String CHAIR_DESCRIPTION = "Chair"; private static String TABLE_DESCRIPTION = "Table"; private static Double TABLE_PRICE = new Double(150.10); private static int POSITIVE_PRICE_INCREASE = 10; protected void setUp() throws Exception { productManager = new SimpleProductManager(); products = new ArrayList<Product>(); // stub up a list of products Product product = new Product(); product.setDescription("Chair"); product.setPrice(CHAIR_PRICE); products.add(product); product = new Product(); product.setDescription("Table"); product.setPrice(TABLE_PRICE); products.add(product); ProductDao productDao = new InMemoryProductDao(products); productManager.setProductDao(productDao); //productManager.setProducts(products); } public void testGetProductsWithNoProducts() { productManager = new SimpleProductManager(); productManager.setProductDao(new InMemoryProductDao(null)); assertNull(productManager.getProducts()); } public void testGetProducts() { List<Product> products = productManager.getProducts(); assertNotNull(products); assertEquals(PRODUCT_COUNT, productManager.getProducts().size()); Product product = products.get(0); assertEquals(CHAIR_DESCRIPTION, product.getDescription()); assertEquals(CHAIR_PRICE, product.getPrice()); product = products.get(1); assertEquals(TABLE_DESCRIPTION, product.getDescription()); assertEquals(TABLE_PRICE, product.getPrice()); } public void testIncreasePriceWithNullListOfProducts() { try { productManager = new SimpleProductManager(); productManager.setProductDao(new InMemoryProductDao(null)); productManager.increasePrice(POSITIVE_PRICE_INCREASE); } catch(NullPointerException ex) { fail("Products list is null."); } } public void testIncreasePriceWithEmptyListOfProducts() { try { productManager = new SimpleProductManager(); productManager.setProductDao(new InMemoryProductDao(new ArrayList<Product>())); //productManager.setProducts(new ArrayList<Product>()); productManager.increasePrice(POSITIVE_PRICE_INCREASE); } catch(Exception ex) { fail("Products list is empty."); } } public void testIncreasePriceWithPositivePercentage() { productManager.increasePrice(POSITIVE_PRICE_INCREASE); double expectedChairPriceWithIncrease = 22.55; double expectedTablePriceWithIncrease = 165.11; List<Product> products = productManager.getProducts(); Product product = products.get(0); assertEquals(expectedChairPriceWithIncrease, product.getPrice()); product = products.get(1); assertEquals(expectedTablePriceWithIncrease, product.getPrice()); } }
We also need to modify the InventoryControllerTests since that class also uses the SimpleProductManager. Here is the modified InventoryControllerTests:
'springapp/test/springapp/service/InventoryControllerTests.java':
package springapp.web; import java.util.Map; import java.util.ArrayList; import org.springframework.web.servlet.ModelAndView; import springapp.domain.Product; import springapp.repository.InMemoryProductDao; import springapp.service.SimpleProductManager; import springapp.web.InventoryController; import junit.framework.TestCase; public class InventoryControllerTests extends TestCase { public void testHandleRequestView() throws Exception{ InventoryController controller = new InventoryController(); SimpleProductManager spm = new SimpleProductManager(); spm.setProductDao(new InMemoryProductDao(new ArrayList<Product>())); controller.setProductManager(spm); //controller.setProductManager(new SimpleProductManager()); ModelAndView modelAndView = controller.handleRequest(null, null); assertEquals("hello", modelAndView.getViewName()); assertNotNull(modelAndView.getModel()); Map modelMap = (Map) modelAndView.getModel().get("model"); String nowValue = (String) modelMap.get("now"); assertNotNull(nowValue); } }
'springapp/web/WEB-INF/springapp-servlet.xml':
<?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-2.5.xsd"> <!-- the application context definition for the springapp DispatcherServlet --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="messages"/> </bean> <bean name="/hello.htm" class="springapp.web.InventoryController"> <property name="productManager" ref="productManager"/> </bean> <bean name="/priceincrease.htm" class="springapp.web.PriceIncreaseFormController"> <property name="sessionForm" value="true"/> <property name="commandName" value="priceIncrease"/> <property name="commandClass" value="springapp.service.PriceIncrease"/> <property name="validator"> <bean class="springapp.service.PriceIncreaseValidator"/> </property> <property name="formView" value="priceincrease"/> <property name="successView" value="hello.htm"/> <property name="productManager" ref="productManager"/> </bean> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean> </beans>
We still need to configure the service layer and we will do that in its own application context file. This file is called 'applicationContext.xml' and it will be loaded via a servlet listener that we will define in 'web.xml'. All bean configured in this new application context will be available to reference from any servlet context.
'springapp/web/WEB-INF/web.xml':
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" > <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>springapp</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springapp</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file> index.jsp </welcome-file> </welcome-file-list> <jsp-config> <taglib> <taglib-uri>/spring</taglib-uri> <taglib-location>/WEB-INF/tld/spring-form.tld</taglib-location> </taglib> </jsp-config> </web-app>
Now we create a new 'applicationContext.xml' file in the 'web/WEB-INF'" directory.
'springapp/web/WEB-INF/applicationContext.xml':
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <!-- the parent application context definition for the springapp application --> <bean id="productManager" class="springapp.service.SimpleProductManager"> <property name="productDao" ref="productDao"/> </bean> <bean id="productDao" class="springapp.repository.JdbcProductDao"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <aop:config> <aop:advisor pointcut="execution(* *..ProductManager.*(..))" advice-ref="txAdvice"/> </aop:config> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="save*"/> <tx:method name="*" read-only="true"/> </tx:attributes> </tx:advice>
The pointcut applies to any method called on the ProductManager interface. The advice is a transaction advice that applies to methods with a name starting with 'save'. The default transaction attributes of REQUIRED applies since no other attribute was specified. The advice also applies "read-only" transactions on any other methods that are adviced via the pointcut.
We also need to define a connection pool. We are using the DBCP connection pool from the Apache Jakarta project. We are reusing the 'jdbc.properties' file we created in Part 5.
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean>
For all this to work we need some additional jar files are present in the ‘Libraries’ directory. If they do not exist, copy aspectjweaver.jar from the 'spring-framework-2.5/lib/aspectj' directory and commons-dbcp.jar and commons-pool.jar from the 'spring-framework-2.5/lib/jakarta-commons' directory to the ‘Libraries’ directory.
Here is the final version of our 'applicationContext.xml' file:
'springapp/web/WEB-INF/applicationContext.xml':
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <!-- the parent application context definition for the springapp application --> <bean id="productManager" class="springapp.service.SimpleProductManager"> <property name="productDao" ref="productDao"/> </bean> <bean id="productDao" class="springapp.repository.JdbcProductDao"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <aop:config> <aop:advisor pointcut="execution(* *..ProductManager.*(..))" advice-ref="txAdvice"/> </aop:config> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="save*"/> <tx:method name="*" read-only="true"/> </tx:attributes> </tx:advice> </beans>
![]() |
Looks just the same as it did before. We did add persistence though, so if you shut down the application your price increases will not be lost. They are still there when you start the application back up.
A lot of work for a very simple application, but it was never our goal to just write this application. The goal was to show how to go about creating a Spring MVC application from scratch and we know that the applications you will create are much more complex. The same steps apply though and we hope you have gained enough knowledge to make it easier getting started to use Spring.
Below is a screen shot of what your project directory structure should look like after following the above instructions.
![]() |
Original version of this tutorial based on Eclipse and Tomcat.
http://www.springframework.org/docs/Spring-MVC-step-by-step/index.html
NetBeans IDE 6.0 Download
http://download.netbeans.org/netbeans/6.0/final/
Spring Netbeans Module Release 1.1
http://spring-netbeans.sourceforge.net/
GlassFish V2 UR1
https://glassfish.dev.java.net/downloads/v2ur1-b09d.html
Download springapp NetBeans Project for the impatient
http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=5171
image001.png | ![]() |
159006 bytes |
image003.png | ![]() |
145265 bytes |
image005.png | ![]() |
150943 bytes |
image007.png | ![]() |
151811 bytes |
image009.png | ![]() |
140896 bytes |
image011.png | ![]() |
162867 bytes |
image013.png | ![]() |
157246 bytes |
image015.png | ![]() |
103351 bytes |
image017.png | ![]() |
179653 bytes |
image019.png | ![]() |
150491 bytes |
image021.png | ![]() |
121746 bytes |
image023.png | ![]() |
153903 bytes |
image025.png | ![]() |
163914 bytes |
image027.png | ![]() |
110847 bytes |
image029.png | ![]() |
134601 bytes |
image031.png | ![]() |
136265 bytes |
image033.png | ![]() |
110459 bytes |
image035.png | ![]() |
125125 bytes |
image037.png | ![]() |
142805 bytes |
image039.png | ![]() |
152525 bytes |
image041.png | ![]() |
132962 bytes |
image043.png | ![]() |
128894 bytes |
image045.png | ![]() |
155804 bytes |
image047.png | ![]() |
125565 bytes |
image049.png | ![]() |
143382 bytes |
inventory-system-classdiagram.png | ![]() |
43440 bytes |
usecase.png | ![]() |
26396 bytes |