Spring Tutorial
Spring Tutorial
Isabelle Muszynski
15 April 2003
Chapter 1
Introduction
This tutorial covers the main packages in the Spring Framework. For full details, we refer you to Rod
Johnson’s book, Expert One-on-One J2EE Design and Development, published by Wrox Press in 2002. The
book’s ISBN number is 1-86100-784-1.
The code for the Spring Framework is contained in package com.interface21. We provide UML diagrams
for the subpackages, as well as code samples.
If you use a different database, read the comments at the top of the samples files, they will direct you to
the spots in the code you will have to change for your database.
1
Chapter 2
Data access is taken care of in the com.interface21.jdbc package and its subpackages. These packages are
non-intrusive, and so you can use them separately from the rest of the framework to implement persistence
in your application.
There are two main packages, com.interface21.jdbc.core, which implements low-level persistence, and the
high-level com.interface21.jdbc.object, which presents an object-oriented view of the low-level package.
2
2.2 The com.interface21.jdbc.object Package
The com.interface21.jdbc.object package provides a hig-level, JDO-like environment for persistence. Queries,
updates and stored procedures are modeled as reusable, threadsafe objects.
The abstract RdbmsOperation class is the root of the hierarchy. It holds a javax.sql.DataSource and a
SQL string as instance variables, and allows bind parameters to be declared. Before being used, a RdbmsOp-
eration must be compiled. The exact meaning of the compilation operation varies between subclasses. After
compilation, no more parameters can be added, but the operation can be performed repeatedly. Parameters
are declared using the declareParameter() method.
The abstract SqlOperation class extends RdbmsOperation and is used as a base class for queries and
updates. The compilation process checks that the number of parameters matches the number of parameter
placeholders in the SQL string.
The SqlQuery class is used for queries; it requires the user to implement method newResultReader,
which should return a ResultReader for the returned ResultSet. The MappingSqlQuery class is a subclass
of SqlQuery which provides a default ResultReader implementation, and is therefore easier to use. When
using MappingSqlQuery, you must implement the mapRow() method which is called once by the framework
for each row in the ResultSet. The SqlUpdate class is used for inserts, updates and deletes.
3
sp_code VARCHAR(10) NOT NULL PRIMARY KEY,
sp_name VARCHAR(30)
);
4
public void update() {
We will now use a MappingSqlQuery to list all rows with code = “HOMEO”. First we create a custom
MappingSqlQuery, MyMappingQuery :
5
spec.setDescription(rs.getString(2));
return spec;
}
}
The query takes one parameter, as indicated by the question mark placeholder. The extract method creates
an instance of Specialty with the data in the returned row.
The method to read the results follows :
We next use a SqlQuery to read all rows. We start by creating an application-specific subclass of SqlQuery,
as SqlQuery is abstract and requires implementation of the newResultReader() method.
We can then use our query object and its ResultReader to read all rows in the table :
public void readAllRows() {
MyQuery query = new MyQuery(ds);
query.compile();
List list = query.execute();
6
System.out.println(‘‘Listing all rows’’);
ListIterator iter = list.listIterator();
while (iter.hasNext()) {
Specialty spec = (Specialty)(iter.next());
System.out.println(‘‘Specialty code = ’’ + spec.getCode() +
‘‘, specialty name = ’’ + spec.getDescription());
}
}
7
2.3.1 The PreparedStatementCreator Interface
JdbcTemplate calls back to client code to create a prepared statement. To this end, the client code must
implement the PreparedStatementCreator interface to return a java.sql.PreparedStatement with correctly
bound parameters.
8
2.3.3 Code Sample
The code sample (tutorial.jdbc.JdbcCorePackage.java) assumes the presence of the following tables:
You should also insert a starting value of zero into the sequence table :
INSERT INTO species_seq VALUES(0);
9
private JdbcTemplate tpl; // the JdbcTemplate instance
private DataSource ds; // the data source
private String dbUrl = ‘‘jdbc:mysql:///test’’; // the JDBC URL
The update() method of JdbcTemplate returns the number of rows affected by the operation. Here, we check
that one row was inserted. We then select all rows from the table using the quey() method of JdbcTemplate.
This method requires a RowCallbackHandler, whose processRow() method will be called once per row. In
our implementation, we simply print the row of data.
10
In order to use a prepared statement, we must pass the JdbcTemplate a PreparedStatementCreator which
prepares the statement and binds the parameters:
public void dynamicInsert() {
PreparedStatementCreator psc =
PreparedStatementCreatorFactory.newPreparedStatementCreator(
‘‘insert into specialty values(?, ?)’’, types, params);
int numRows = tpl.update(psc);
if (1 == numRows) {
11
System.out.println(‘‘One row was inserted.Rows in table are:’’);
// Need a RowCallbackHandler
tpl.query(‘‘select * from specialty’’,
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
System.out.println(‘‘Specialty code = ’’ +
rs.getString(1) + ‘‘, specialty name = ’’ +
rs.getString(2));
}
}
);
}
The PreparedStatementCreatorFactory needs to know the number and SQL types of the parameters, as well
as the values of the paramaeters to be bound.
Next, we list all results. We use a helper class to represent one row of data, class Specialty :
12
tpl.query(‘‘select * from specialty’’, reader);
PreparedStatementCreator psc =
PreparedStatementCreatorFactory.newPreparedStatementCreator(
"insert into species values(?, ?)", types, params);
tpl.update(psc);
System.out.println("Inserted species with id = " + params[0]);
}
// List all rows
tpl.query("select * from species",
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
System.out.println("Species id = " + rs.getInt(1) +
", species name = " + rs.getString(2));
}
}
);
13
}
The MySQLMaxValueIncrementer reads keys from the database in bunches of 2 values, as specified by the
last argument to the constructor. In each iteration of the insertion loop, we bind a key value and a species
to the prepared statement.
14
Chapter 3
The com.interface21.beans package and its subpackages provide for specifying the infrastructure of your
project using Java Beans. Beans are used extensively throughout the framework, as will be demonstrated in
following tutorials.
There are three main packages, com.interface21.beans, com.interface21.beans.factory which defines bean
factories, and com.interface21.beans.propertyeditors which defines a number of property editors (we will
discuss property editors later in this tutorial).
A Java Bean is simply a class with a default constructor, and which follows a naming convention where
a property named “prop” has a setter setProp and a getter getProp. We refer you to the Sun tutorials for
more information :
• http://developer.java.sun.com/developer/onlineTraining/Beans/bean01/
• http://developer.java.sun.com/developer/onlineTraining/Beans/bean02/
• http://developer.java.sun.com/developer/onlineTraining/Beans/bean03/
15
3.2 The sample classes
The example in this tutorial uses the following interfaces and implementation classes :
16
3.2.3 The IOwner interface
public interface IOwner {
void setName(String name);
String getName();
void setPets(List pets);
List getPets();
}
17
this.age = age;
}
public void setSpecies(ISpecies sp) {
species = sp;
}
public ISpecies getSpecies() {
return species;
}
private String name;
private int age;
private ISpecies species;
}
18
2. Getting or setting nested properties
3. Providing a standard method to perform initialization after all bean properties have been set
The main classes in the package is the BeanWrapper interface and its default implementation, Bean-
WrapperImpl. A BeanWrapper wraps a Java Bean and provides facilities for manipulating it.
19
3.3.1 Code Sample
The code sample (tutorial.beans.TestBeans.java) starts out by manipulating some beans through bean wrap-
pers.
We start by creating some beans, using their default constructors. We then use bean wrappers to set
their properties. Of special note is the setting of the nested Species property of the pets, bodo and pixel.
The notation used to denote nesting is the dot, i.e. “species.name”.
20
We then print out all of the owner’s pets.
21
} catch (PropertyVetoException e) {
e.printStackTrace();
}
System.out.println("Property editor : " + isabelle.getName() + "’s pets are: ");
ListIterator iter = isabelle.getPets().listIterator();
while (iter.hasNext()) {
IPet pet = (IPet)iter.next();
ISpecies sp = pet.getSpecies();
System.out.println(pet.getName() + " of species " + sp.getName());
}
}
Now, instead of specifying all the pet properties separately, we provide them as one string.
22
<?xml version="1.0"?>
<beans>
</beans>
Note the beanRef attribute on the species property of the pets. It signifies that the property refers to a
bean definition somewhere in the file.
23
// Find bean isabelle
Owner isabelle = (Owner)fac.getBean("isabelle");
System.out.println("Found bean: " + isabelle.getName() + " with pets: ");
ListIterator iter = isabelle.getPets().listIterator();
while (iter.hasNext()) {
IPet pet = (IPet)iter.next();
ISpecies sp = pet.getSpecies();
System.out.println(pet.getName() + " of species " + sp.getName());
}
}
24
Chapter 4
The com.interface21.context package and its subpackages build on the beans package, allowing beans to be
stored in a standard place which is accessible from your application. The application context also allows
storage of objects for later retrieval, and lookup of messages in message files. This is a very important
feature for internationalizing an application. Application contexts are hierarchical, i.e. a context can have a
parent context. When looking up an object, if it isn’t found in the current context the parent contexts will
be searched until the root of the tree is reached.
Application contexts also allow for the publication of events to registered listeners.
The com.interface21.context.support package contains implementations of the interfaces defined in the
com.interface21.context package.
25
4.1.1 The root application context, appContext.xml
<?xml version="1.0"?>
<beans>
</beans>
<beans>
<bean name="messageSource"
class="com.interface21.context.support.ResourceBundleMessageSource">
<property name="basename">messages</property>
</bean>
26
<property name="name">Raphael</property>
<property name="age">6</property>
<property name="species" beanRef="true">cat</property>
</bean>
</beans>
Note that the bean references to cat and dog refer to beans found in the parent context. This file also
contains a message source bean; the name property refers to the name of the message file.
The file is located in the classes directory, so that it can be found at runtime.
The second message contains an argument, which should be formatted as a date. For details on the
supported syntax, see the documentation of class java.text.MessageFormat.
public AppContext() {
PropertyEditorManager.registerEditor(List.class, OwnerEditor.class);
String sep = System.getProperty("file.separator");
// The parent context
StringBuffer buff1 = new StringBuffer();
buff1.append("src").append(sep).append("tutorial").append(sep).
append("context").append(sep).append("appContext.xml");
// The child context
StringBuffer buff2 = new StringBuffer();
buff2.append("src").append(sep).append("tutorial").append(sep).
append("context").append(sep).append("babyContext.xml");
try {
ctx = new FileSystemXmlApplicationContext(new String[]
{buff1.toString(), buff2.toString()});
} catch (IOException e) {
e.printStackTrace();
}
}
Since we have 2 contexts, we pass a string array containing the parent and child contexts to the constructor
of the application context.
We then check that our beans have been correctly loaded into the application context :
27
public void listBeans() {
try {
IPet pet = (IPet)ctx.getBean("bodo");
if (null != pet)
System.out.println("Found " + pet.getName() + " of species " +
pet.getSpecies().getName());
pet = (IPet)ctx.getBean("raphael");
if (null != pet)
System.out.println("Found " + pet.getName() + " of species " +
pet.getSpecies().getName());
pet = (IPet)ctx.getBean("pixel");
if (null != pet)
System.out.println("Found " + pet.getName() + " of species " +
pet.getSpecies().getName());
We next try to store a custom object into the application context, and check that we can retrieve it :
28
Object[] arguments = {new Date(System.currentTimeMillis())};
System.out.println(ctx.getMessage("tutorial.context.othermsg",
arguments, Locale.getDefault()));
} catch(NoSuchMessageException e) {
e.printStackTrace();
}
}
The source parameter to the constructor is the object which caused the event to be published. In our
case, it will be an Owner.
We modify the Pet class from the beans tutorial to be an event listener :
29
Owner owner = (Owner) ev.getSource();
String name = owner.getName();
switch(ev.getType()) {
case FoodEvent.BREAKFAST:
System.out.println("Hurrah, " + name +
" says it’s breakfast time");
break;
case FoodEvent.DINNER:
System.out.println("Hurrah, " + name +
" says it’s dinner time");
break;
}
}
}
Class Owner needs to become “application context aware”, so that it can cause an event to be published.
This involves implementation of interface ApplicationContextAware :
and
where the ctx member variable is of type ApplicationContext. The Owner class will then use this context
to publish events.
We next add a feedPets() method to Owner :
The method creates a FoodEvent and publishes it through the application context.
In order to test our event mechanism, we add the following method to AppContext.java :
30
e.printStackTrace();
}
}
31
Chapter 5
This chapter of the tutorial introduces you to building web applications with the Spring framework. The ap-
plication we develop in this chapter doesn’t do anything useful, but shows you how to set up the environment
correctly and can serve as a skeleton for your own applications.
32
• deploy
• install
• list
• reload
• remove
• resources
• roles
• start
• stop
• undeploy
You may need to copy Tomcat’s server/lib/catalina-ant.jar to ant’s lib directory.
All the source files are contained in the src subdirectory and its subdirectories. The web subdirectory
contains the web content of the application (html and jsp files). The metadata subdirectory contains various
xml and property files, which we will come back to later.
<beans>
<!-- ===========================================================-->
<!-- Global Message source. For all servlets. -->
<!-- ===========================================================-->
<bean name="messageSource"
class="com.interface21.context.support.ResourceBundleMessageSource">
<property name="basename">messages</property>
</bean>
</beans>
33
5.3.2 skeleton-servlet.xml
Each servlet using the framework has its own application context, which is a child of the root context. This
context is defined by a file with a name of the form [servletName]-servlet.xml. Since our servlet will be called
skeleton, the application context is defined in skeleton-servlet.xml.
The sample file contains the definition of another message source, more-messages.properties, and the
definition of the view resolver, which is required by the framework. The view resolver definition refers to
views.properties, which contains mappings for views in our application. Finally, we have a section defining
the mapping from request URL’s to controller beans, and the definition of the controller beans themselves.
In this example, we have two controller servlet, SkeletonController and HelloController. HelloController
demonstrates the use of Java bean properties.
<beans>
<!-- ===========================================================-->
<!-- Message source -->
<!-- ===========================================================-->
<!-- Messages will be taken from classpath.
Place file under /WEB-INF/classes in the WAR
CAN OVERRIDE
-->
<!--
<bean name="messageSource"
class="com.interface21.context.support.ResourceBundleMessageSource">
<property name="basename">more-messages</property>
</bean>
-->
<!-- ===========================================================-->
<!-- View resolver. Required by web framework. -->
<!-- ===========================================================-->
<bean name="viewResolver"
class="com.interface21.web.servlet.view.ResourceBundleViewResolver">
<!--
A false value is for development only:
REPLACE BY true OR COMMENT OUT IN PRODUCTION OR
performance will be severely impacted. **************
-->
<property name="cache">true</property>
<!--
We can use this to avoid conflict with other command servlets.
It’s the name of the resource bundle loaded from the classpath.
-->
34
<property name="basename">views</property>
</bean>
<!-- ===========================================================-->
<!-- Listeners to this servlet only -->
<!-- ===========================================================-->
<!--
Use this listener for debugging only:
comment out in production.
-->
<bean name="consoleListener"
class="com.interface21.context.support.ConsoleListener"
/>
<!-- ===========================================================-->
<!-- URL mappings for web framework. -->
<-- ============================================================-->
<--
Simple HandlerMapping implementation that maps from request URL
to handler bean name in this application context.
Handlers can have any bean name, and are applied in order of the
"order" property
This application uses only one handler map.
-->
<bean name="urlMap"
class="com.interface21.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
/skeleton.html=skeletonController
</property>
</bean>
<!-- ===========================================================-->
<!-- Controllers -->
<!-- ===========================================================-->
<bean name="skeletonController"
class="tutorial.web.skeleton.SkeletonController" >
</bean>
<!--
Illustrates use of a JavaBean property. See page 482.
-->
<bean name="helloController"
35
class="tutorial.web.skeleton.HelloController" >
<property name="name">The Bean Controller</property>
</bean>
</beans>
Class Diagrams
The controller, resolver and url mapper class diagrams are shown below.
36
37
5.3.3 web.xml
The web.xml file is a required part of every application. It contains definitions of initial parameters to pass
to the web context, listener definitions and servlet definitions.
38
<!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>
<display-name>Skeleton</display-name>
<description>Skeleton web application for Spring tutorial</description>
<listener>
<listener-class>com.interface21.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
This is the controller servlet that handles all incoming requests,
delegating work to Controller implementations according to mappings in the
skeleton-servlet.xml file.
-->
<servlet>
<servlet-name>skeleton</servlet-name>
<servlet-class>com.interface21.web.servlet.ControllerServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!--
39
Mapping of URLs onto the controller servlet.
-->
<servlet-mapping>
<servlet-name>skeleton</servlet-name>
<url-pattern>*.i21</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>60</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!--
Display this page to handle unexpected exceptions gracefully.
Otherwise we get whatever error page the container chooses.
-->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/jsp/uncaughtException.jsp</location>
</error-page>
</web-app>
The framework depends on a context listener, the ContextLoaderListener, which will load the application
context from the context-param URL specified at the top of the file, and the controller servlet that handles
all incoming requests. Our controller servlet has a servlet-name of skeleton. In the servlet-mapping section,
we define that all requests for pages with an extension of i21 will be handled by our controller servlet. We
could also have specified an extension of html, but then requests for static html pages would also have been
handed out to our controller. A way out of this is to use a different extension for static pages, such as htm.
5.3.4 messages.properties
The format of this file is very simple. An identifier for a message is followed by an equals sign and its
definition.
tutorial.webapps.skeleton.greeting=I am a skeleton
We do not use messages in this chapter of the tutorial, but at least now you know the format of the
message file.
5.3.5 views.properties
This file defines the mappings from URL’s to views.
40
# Format is
skeletonView.class=com.interface21.web.servlet.view.InternalResourceView
skeletonView.url=/skeleton.html
greetingView.class=com.interface21.web.servlet.view.InternalResourceView
greetingView.url=/greeting.jsp
enterNameView.class=com.interface21.web.servlet.view.InternalResourceView
enterNameView.url=/enterName.jsp
5.3.6 log4j.properties
In the log4j property file, we can refer to the context-param we set in web.xml to configure the location of
the log file :
log4j.rootCategory=info, stdout
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=${skeleton.root}/WEB-INF/skeleton.log
log4j.appender.R.MaxFileSize=100KB
# Keep one backup file
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=***%C : %M%n
This controller is extremely simple: it passes control to the view mapping defined by “skeletonView” in
views.properties, i.e. skeleton.html.
41
HelloController.java
public class HelloController
extends AbstractController {
/**
* JavaBean property, set through XML configuration descriptor
*/
public void setName(String name) {
this.name = name;
}
/**
* This is the main request handling method
*/
protected ModelAndView handleRequestInternal(
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
String pname = request.getParameter("name");
if (pname == null) {
return new ModelAndView("enterNameView");
}
else {
return new ModelAndView("greetingView", "greeting",
"Hello " + pname + ", my name is " + this.name);
}
}
/**
* Overridden method that is invoked after bean properties
* are set, but before we begin handling requests.
*/
protected void init() throws ApplicationContextException {
if (this.name == null)
throw new ApplicationContextException(
"name property must be set on beans of class " + getClass().getName());
}
}
This controller dispatches to one of two views, depending on whether the name property is set. Of
particular interest is the call to the “greetingView” view. The second parameter, “greeting”, is the name of
the model passed to the view. The third parameter is an Object representing the value of the model. This
model is used in greeting.jsp.
42
5.4 greeting.jsp
<jsp:useBean id="greeting" type="java.lang.String" scope="request" />
<%=greeting%>
43
Chapter 6
In the previous chapter, we used a separate controller per request. Sometimes, however, it is more convenient
to use a single controller for multiple requests. This is the job of the MultiActionController.
This chapter presents a simple example of one controller, MultiController, handling two requests. The
second request also shows you how to return output from the controller itself, instead of dispatching to a
view. This is not something you would usually do, but the facility is there if you ever need it.
The source code for this chapter is in webapps/multi action.
6.1 web.xml
The only relevant changes to web.xml are
<!--
This is the controller servlet that handles all incoming requests,
delegating work to Controller implementations according to mappings in the
multi-action-servlet.xml file.
-->
<servlet>
<servlet-name>multi-action</servlet-name>
<servlet-class>
com.interface21.web.servlet.ControllerServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
6.2 multi-action-servlet.xml
The relevant changes to multi-action-servlet.xml are the url mappings and the definition of the multiple
actions.
44
<!-- ===========================================================-->
<!-- URL mappings for web framework. -->
<!-- ===========================================================-->
<!--
Simple HandlerMapping implementation that maps from request URL
to handler bean name in this application context.
Handlers can have any bean name, and are applied in order of
their "order" property
This application uses only one handler map.
-->
<bean name="urlMap"
class="com.interface21.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
/debug.i21=multiActionController
/build.i21=multiActionController
</property>
</bean>
<!-- ===========================================================-->
<!-- Controllers -->
<!-- ===========================================================-->
<!--
Map from application URLs to method names in the MultiActionController class.
Analogous to Struts LookupDispatchAction approach.
See page 485 for discussion of this framework capability.
-->
<bean name="multiActionMethodNameResolver"
class="com.interface21.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
<property name="mappings">
/something.i21=doSomething
/build.i21=buildResponse
</property>
</bean>
<!--
Controller bean that handles multiple request types.
See page 484 for discussion of this framework capability.
-->
<bean name="multiActionController"
class="tutorial.web.multi_action.MultiController" >
<!--
Reference to another bean in this factory.
We can use this syntax to define any references
45
among our application objects.
-->
<property name="methodNameResolver"
beanRef="true">multiActionMethodNameResolver</property>
</bean>
The multiActionMethodNameResolver bean defines the mapping from requests to methods in the Mul-
tiAction controller.
6.3 MultiController.java
public class MultiController extends MultiActionController {
/**
* Demonstrates a simple request handling method.
*/
public ModelAndView doSomething(HttpServletRequest request, HttpServletResponse response) {
return new ModelAndView("someView");
}
/**
* This controller method shows how any controller
* can build the request itself, if it returns null.
* Of course, usually it’s best to let a view handle
* result generation--using a view gives us an extra
* level of indirection that is often useful.
*/
public ModelAndView buildResponse(HttpServletRequest request,
HttpServletResponse response) throws IOException {
ServletOutputStream sos = response.getOutputStream();
sos.println("This response was generated by " +
"the controller " + this +
".\n As we normally don’t want to generate " +
"content in Java classes, this is an unusual special case. " +
"It’s most appropriate for some binary formats. " +
"\nWhen we want to build the response in a controller, " +
"we return null. This works for ordinary controllers and" +
"MultiActionControllers.");
sos.close();
46
Note that you return null from the controller to let the framework know that the controller built the
response itself.
47