Professional Documents
Culture Documents
07 Dec 2004
Java developer and trainer Roy Miller revamps our existing introductory servlet
material into this single easy-to-follow, hands-on tutorial. Roy introduces explains
what servlets are, how they work, how you can use them to create Web applications
of any degree of sophistication you can imagine, and how you can use servlets most
effectively as a professional programmer.
The content of this tutorial is geared toward Java programmers who are unfamiliar,
or only vaguely familiar, with servlets. It assumes a general knowledge of
downloading and installing software, and a general knowledge of the Java language
If you've been writing Web apps for a decade, this tutorial is not for you. If you don't
know what servlets are, or are only vaguely familiar with them, keep reading. There
is much more to servlets than what is included in this tutorial, but this is a good place
to start your learning.
You should, however, have the fundamentals of Java programming well in hand. If
you're not quite there yet, try my Introduction to Java programming tutorial to get
started.
All code examples in this tutorial have been tested with J2SE 1.4.2 on the Windows
XP platform, but should work without modification using J2SE 1.4.1, or even 5.0.
To install Tomcat, go to the Jakarta Web site (see Resources) and download the
binary distribution of Tomcat 5.0.28 (the most current version that works with J2SE
1.4.2, as of this writing). The package comes with a Windows installer that makes
installing on that platform a breeze. Follow the instructions in the readme files, and
you'll be set.
To install the Tomcat plugin for Eclipse, go to the Sysdeo Web site (see Resources )
and download the plugin zip file (tomcatPluginV3.zip, as of this writing). Then simply
extract it to your plugins directory, and follow the instructions at the bottom of the
download page to set up the plugin. To be sure your plugin is working correctly, work
through the very simple HelloWorld servlet setup "tutorial" that is linked at the bottom
of the Sysdeo page (see Resources for a direct link).
Once you have Tomcat and the plugin installed, you're ready to begin this tutorial.
He began his career at Andersen Consulting (now Accenture), and most recently
spent three years using the Java platform professionally at RoleModel Software,
Inc., in Holly Springs, NC. He has developed software, managed teams, and
coached other programmers at clients ranging from two-person start-ups to Fortune
50 companies.
For technical questions or comments about the content of this tutorial, contact Roy
at roy@roywmiller.com.
A servlet is the gatekeeper for that process. It lives on the Web server and handles
incoming requests and outgoing responses. It has nothing to do with presentation,
generally speaking, and really shouldn't. You can use a servlet to write to a stream
that adds content to a Web page, but that's usually not a good idea either, because it
tends to encourage mixing presentation with business logic.
Servlet alternatives
Servlets aren't the only way to serve up Web pages. One of the earliest technologies
for this purpose was the common gateway interface (CGI), but that forked a
separate process for each request, which wasn't very efficient. There were also
proprietary server extensions like the Netscape Server API (NSAPI), but those were,
well, proprietary. And in the Microsoft world, there is the active server pages (ASP)
standard. Servlets provide an alternative to all of these, and they offer several
advantages:
servlets spawn new threads, rather than separate processes, for requests
• There is extensive industry support for servlets, including containers for
most popular Web and application servers
Servlets are a powerful addition to the professional programmer's toolbox.
When you create a Java servlet, you typically subclass HttpServlet. This class
has methods that give you access to the request and response wrappers you can
use to handle requests and create responses.
A container, like Tomcat, manages the runtime environment for servlets. You can
configure the container to customize the way in which the J2EE server functions,
and you must configure it to expose your servlets to the world. As we'll see, through
various configuration files in the container, you provide a bridge from a URL (entered
by a user in a browser) to the server-side components that handle the request that
you want the URL to translate into. When your app runs, the container loads and
initializes your servlet(s), and manages their lifecycle.
When we say that servlets have a lifecycle, we simply mean that things happen in a
predictable way when a servlet is invoked. In other words, certain methods on any
servlet you create will always get called in the same order. Here's a typical scenario:
• A user enters a URL in his browser. Your Web server configuration file
says that this URL points to a servlet managed by a servlet container
running on your server.
• If an instance of the servlet hasn't been created yet (there's only one
instance of a servlet for an application), the container loads the class and
instantiates it.
• The container calls init() on the servlet.
• The container calls service() on the servlet, and passes in a wrapped
HttpServletRequest and HttpServletResponse.
• The servlet typically accesses elements in the request, delegates to other
server-side classes to perform the requested service and to access
resources like databases, then populates the response using that
information
• If necessary, when the servlet's useful life is done, the container calls
destroy() on the servlet to finalize it.
Our first servlet will do very little, but it will expose all of the basics of writing a
servlet. The output will be some simple, unformatted text in a browser window:
Hello, World!
In creating this servlet, we'll be able to confirm that Tomcat functions as it ought to,
and that we can use Eclipse to create a Web project like we should be able to. We'll
also walk through the process of configuring your Web app in the Tomcat servlet
container, which can be a bear if you happen make a slight mistake in an XML file.
Don't worry: In this tutorial, at least, it will all work.
In this first example, we'll write output to the browser directly from our servlet. This
will be the last time in the tutorial that we use that approach.
Setting up Eclipse
There are a few things we need to do to make sure we can create and manage
Tomcat projects in Eclipse.
If you've installed the plugin (by simply extracting the Sysdeo zip file to your
eclipse/plugins directory), you should get some additional menu items and tools on
your toolbar. These are indicated in Figure 1.
The toolbar buttons let you start, stop, and restart Tomcat, which you'll have to do
when you want to run your servlets.
To allow us to create Tomcat projects, which have the correct layout to facilitate
Select Version 5.x, and specify the Tomcat home location. (On my system, this
location is C:\Program Files\Apache Software Foundation\Tomcat 5.0, but yours
may vary.) Select Context files as the context declaration mode. Then click on the
JVM Settings preferences subcategory and make sure there's a valid JRE selected
in the drop-down menu at the top of the page. You can use the default JRE, or you
can point to your JDK, which you can tell Eclipse about in the Java>Installed JREs
preferences page.
When you're done, click OK. We're now ready to create a Tomcat project.
The Tomcat plugin makes life much easier for the Web developer using Tomcat. If
you click File>New>Project, and expand the Java wizard category in the dialog
(see Figure 3), you'll see a new kind of a project wizard there: a Tomcat project.
Click Next, name the project "HelloWorld," then click Finish. If you switch to the
Java perspective in Eclipse, you'll be able to see that new project. It has a structure
that will facilitate deployment to Tomcat (see Figure 4).
The work, WEB-INF, and WEB-INF/src directories are particularly important, as we'll
see later.
Testing Tomcat
Click the Start Tomcat toolbar button. Eclipse should update your console with
informational statements as Tomcat tries to launch. If it launches without showing
any stack traces, you're set. If you see any stack traces in there, life becomes
slightly more difficult. Unfortunately, trial and error (with your good friend Google) is
the only way to track down any errors that occur. The good news is that starting with
a fresh, new project like we did should eliminate the possibility of any nasty errors.
When Tomcat starts, you won't see anything (except the console content). You'll
have to test it to be sure it's working. If you want a quick indication, try opening a
browser and entering the following URL:
http://localhost:8080/
If all goes well, you should see either a nice Tomcat welcome page, or a directory
listing for the Tomcat "ROOT context." Don't worry about the second one. We'll
prove Tomcat is working when we run our first servlet.
HttpServletResponse response)
throws ServletException, IOException {
PrintWriter writer = response.getWriter();
writer.println("Hello, World!");
writer.close();
}
}
Enter this code, then press Ctrl+Shift+O to organize your import statements.
Eclipse should give you imports for the following classes:
• java.io.IOException
• java.io.PrintWriter
• javax.servlet.ServletException
• javax.servlet.HttpServlet
• javax.servlet.HttpServletRequest
• javax.servlet.HttpServletResponse
Notice that we subclassed HttpServlet, and that we overrode the service()
method. The service() method is the most basic processing method that the
servlet engine will call in our servlet's lifecycle. It takes a request wrapper and a
response wrapper, which we can access in our method. We have no need to do so
here, though, since we're just doing something basic to get a servlet working. We
could have overridden doGet(), but service() will give us what we need.
Right-click on the HelloWorld project and select Properties. Select the Tomcat
category of properties. You should see a context for the project that looks something
like this:
/HelloWorld
Now go look at the filesystem in your Tomcat home. Drill down to the
When Tomcat launches, it reads these context files to tell the servlet container
where to find your classes (which include your servlets). If you look back at the INFO
statements Tomcat spits out to the console when it's loading, you'll see information
about your Web application context in the list.
The last step for configuring your Web application in Tomcat is to create a web.xml
file, which needs to live in the WEB-INF directory of your project. (Note: Do not put it
in the WEB-INF/src directory -- that's for other things.) Here's what the file should
look like for this simple example:
This file defines your Web application to Tomcat. The servlet-name element
names your servlet for use in this file. The servlet-class element maps that
name to a particular class that defines the servlet -- HelloWorldServlet, in our
example. The servlet-mapping element tells Tomcat that URLs of the form (in
this case) /hello map to our servlet, which is defined by the mapped servlet class.
Once we have this file in place, we can fire up Tomcat and see our servlet load.
http://localhost:8080/HelloWorld/hello
Introduction
In the early days of Web development, many professional programmers had to
figure out how to use servlets well as they went along. One of the most common
results was an explosion of servlets on the server. There was one for each type of
request.
In an action servlet, you don't have conditional logic that directs the servlet's
behavior. Instead, you have actions (programmer-defined classes) that the servlet
delegates to in order to handle different types of requests. Most of the time, that's a
much better object-oriented (OO) approach than having multiple servlets, or multiple
if conditions in a single servlet.
when we enter the URL that accesses the servlet. If you configured Tomcat to use
context files, it should automatically create one for this project.
Eclipse also should have created a project that has the correct structure, with the
following important directories:
• WEB-INF
• WEB-INF/src
• work
The first directory (WEB-INF) stores important configuration files, specifically the
web.xml file that we'll discuss later. It also contains our compiled code, in the classes
directory. The second directory (WEB-INF/src) stores the source code for our Java
classes. The third directory (work) contains the compiled code for our JavaServer
Pages (JSP) files, which Tomcat creates automatically for us whenever we hit a JSP
page for the first time after code has changed (we'll talk about JSP technology more
in the next section). The root of the project contains all of our JSP source files, as
well as our database file.
Note that you can see all of this structure in the Resource perspective in Eclipse, but
you'll see only the WEB-INF/src and work directories in the Java Browsing
perspective.
All of these files are contained in the contacts.jar file included with this tutorial (see
Resources for a link). To import them, simply create a new Tomcat project, then
import contacts.jar (use the Import>Zip file option). That will bring all of the files in
at the right locations, except the source code. The source code will end up in the src
directory at the root of the project. Move the contents of that folder to WEB-INF/src
and you should be all set.
Presentation
This is a tutorial about servlets, after all, which have almost nothing to do with
presentation. Still, without seeing some results on the screen somewhere, we'd
really be telling only part of the story. You certainly can write servlets that aren't
involved with presentation at all, but most Web apps present information in a
browser, which means that you have to choose a presentation mechanism to use.
JavaServer Pages technology is one typical alternative and is used widely.
With JSP technology, you can create dynamic Web pages. They support static
HTML (or other markup, such as XML) and dynamic code elements that, as the
name implies, can create content dynamically. Under the covers, JSP pages are
compiled into servlets (that is, into Java code) by a container like Tomcat. You
almost never will have to care about that, however. Just know that the following flow
occurs:
• A user types a URL in a browser that the J2EE servlet container points to
a servlet
• The servlet does its job and puts information in the session, or in a bean,
and forwards to the JSP page
• The JSP code translates information in the bean and/or the session, and
sends the response to the browser
You can create simple JSP pages easily and run them in Tomcat with only minor
configuration changes in our Web app and without downloading additional code
libraries, so we'll use them here (see Resources for much more detailed information
about JSP technology).
Our Contacts application will have one primary JSP page or listing existing contacts
and adding new ones. Later, we'll add pages for login and logout.
It's important to remember that JSP technology is just one presentation alternative.
There are others. One that is gaining great popularity is the Jakarta Velocity
templating package (see Resources). JSP technology does have a major drawback,
which is that complicated, feature-rich apps tend to require ridiculously complex JSP
pages, along with some extra server work to create custom tags if you want to keep
your logic and presentation separate. Another drawback is that JSP technology
offers a frequently irresistible temptation to mix business logic and presentation,
which makes for brittle systems that bring on maintenance nightmares.
In my opinion, JSP technology is frequently the wrong choice, and Velocity (or some
other templating approach) is frequently the right one. But for our simple example, it
will serve the purpose of illustrating the concepts we need to cover. In such a simple
case, mixing a little logic and a little presentation is acceptable. Professionally,
however, it's unwise most of the time, even though many programmers do it.
</servlet>
<servlet-mapping>
<servlet-name>contacts</servlet-name>
<url-pattern>/index.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>contacts</servlet-name>
<url-pattern>*.perform</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>jspAssign</servlet-name>
<servlet-class>
org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>logVerbosityLevel</param-name>
<param-value>WARNING</param-value>
</init-param>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jspAssign</servlet-name>
<url-pattern>/*.jsp</url-pattern>
</servlet-mapping>
</web-app>
We created a basic web.xml file for our HelloWorldServlet, but it was the pretty
minimal. As your application becomes more complex, your web.xml file has to
become more savvy. Let's analyze this file quickly.
The <servlet> tag specifies a name alias for our servlet that we'll use elsewhere in
the file. It also tells Tomcat which class to instantiate so as to create the servlet in
memory. In my Eclipse workspace, I created a
com.roywmiller.contacts.model2 package to hold the servlet class. You can
call your package whatever you want, but the path to your servlet has to match
what's in your <servlet-class> element here. The second servlet we define is
one that comes with Tomcat when you download it, and you don't need to change it.
It's simply the JSP-handling servlet.
In our simple example, we're not going to spend much time talking about JSP
technology. That's to ensure that we keep things simple, and don't get bogged down
in the details of presentation generally and JSP technology in particular. (Again, see
Resources for more information.) We're also going to put everything on a single
page, at least initially, even though that's somewhat unrealistic. That should
minimize the number of pages we have to create just to illustrate the important
concepts of using servlets.
Our initial page will present the list of contacts, which will come from an object that
contains the list. It will also contain a form for adding a new contact. The page will
look like Figure 5.
While not a work of art, the page displays all of our contacts in nicely formatted rows
at the top. Each one has a Delete link that the user can click on to delete that
particular contact. The form contains fields for name and address values, and radio
buttons for the type of contact (family or acquaintance, in our simple example). This
simple page will allow us to explore how to use a simple action framework in our
servlet application. It also will let us explore how to use the request and response
our servlet receives from the browser during a user session.
</tr>
<%
}
%>
</table>
<br/>
<br/>
<br/>
<fieldset>
<legend><b>Add Contact</b></legend>
<form method="post" action="addContactAction.perform">
<table>
<tr>
<td>First Name:<td>
<td><input type="text" size="30"
name="firstname"></td>
</tr>
<tr>
<td>Last Name:<td>
<td><input type="text" size="30"
name="lastname"></td>
</tr>
<tr>
<td>Street:<td>
<td><input type="text" size="30"
name="street"></td>
</tr>
<tr>
<td>City:<td>
<td><input type="text" size="30"
name="city"></td>
</tr>
<tr>
<td>State:<td>
<td><input type="text" size="30"
name="state"></td>
</tr>
<tr>
<td>Zip:<td>
<td><input type="text" size="30"
name="zip"></td>
</tr>
<tr>
<td>Type:<td>
<td><input type="radio" size="30"
name="type" value="family">
Family <input type="radio" size="30"
name="type" value="acquaintance"
checked> Acquaintance</td>
</tr>
</table>
<br/>
<input type="submit" name="addContact" value=" Add ">
</form>
</fieldset>
</body>
</html>
At this point, most of what you see there is probably Greek. We won't dissect all of it,
but over the next few sections we'll hit the high points for understanding how our
servlet will interact with this page.
In a JSP page, HTML is HTML. Java code embedded in the page appears like so:
In order to embed Java code in the page, you have to tell the JSP page where the
classes are, just like you do in a Java class. You do that with statements like this:
Our page displays a list of contacts, which come from a ContactList instance that
the JSP page knows about because of this line:
This line tells the JSP page to use a bean, called contacts elsewhere in the page.
It's an instance of com.roywmiller.contacts.model.ContactList, and it
has session scope.
Notice that we have a Java for loop in the body of the page:
This illustrates how JSP technology lets you mix HTML and Java statements. Here
we loop through the contact list of our contact object. Each time through the loop,
we add a <tr> element to our HTML table. Within each row of the table, one per
contact, we call getters on the Contact instance to populate our table cells. For the
first cell, we need to create a Delete link for each row. We set the href attribute to
the following string:
removeContactAction.perform?id=<%= contact.getId()%>
When a user clicks that link, that string will be appended to the end of the URL that
gets sent to the server, after an initial slash (/) is added. The question mark is a
delimiter for request parameters, which follow in name=value pairs. In this case, we
send along the ID of each contact.
This same kind of thing happens elsewhere in the page, such as in the form to add a
new contact. Notice the <form> tag:
When a user clicks the Add button (the submit button at the bottom of the form),
addContactAction.perform gets appended to the URL.
That's all there is to it! Some of this syntactical magic is part of the reason many
professional programmers either begrudgingly use JSP technology, or create
various helper classes (such as custom JSP tags) that help make pages easier to
create, read, and maintain. But now that we have the page, we can get to writing
some code.
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.roywmiller.contacts.actions.Action;
public class ContactsServlet extends HttpServlet {
protected ActionFactory factory = new ActionFactory();
public ContactsServlet() {
super();
}
protected String getActionName(HttpServletRequest request) {
String path = request.getServletPath();
return path.substring(1, path.lastIndexOf("."));
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws
ServletException, IOException {
Action action = factory.create(getActionName(request));
We extend HttpServlet and override the service() method, just like before. In
that method, we:
• Derive the action name from the URL that caused the servlet to be
invoked
• Instantiate the correct action based on the name
• Tell the action to perform itself
• Forward the response to the URL to which our action points us
We derive the action name from the URL that caused the servlet to be invoked,
which we get from request.servletPath(). Remember that all of the URLs that
cause us to invoke an action will have the form *.perform. We parse that to get
the string to the left of the dot, which is the action name, then pass that action name
to our ActionFactory to instantiate the right action. Now you see why we told our
Web app how to handle URLs of that form, and why we used those "magic" strings
in our JSP page. It was so we could decode them here and use actions to our
advantage. What's the alternative? Lots and lots of if statements, and lots of extra
code. With actions, as we'll see, each action we need to perform is neatly
encapsulated.
This is fine, but we need some additional classes to get the job done. That's where
our action framework comes in.
In the service() method of our servlet, the process begins with ActionFactory.
The ActionFactory
Here's our ActionFactory:
import java.util.HashMap;
import java.util.Map;
import com.roywmiller.contacts.actions.Action;
import com.roywmiller.contacts.actions.AddContactAction;
import com.roywmiller.contacts.actions.BootstrapAction;
import com.roywmiller.contacts.actions.RemoveContactAction;
public class ActionFactory {
protected Map map = defaultMap();
public ActionFactory() {
super();
}
public Action create(String actionName) {
Class klass = (Class) map.get(actionName);
if (klass == null)
throw new RuntimeException(getClass() + " was unable to find
an action named '" + actionName + "'.");
Action actionInstance = null;
try {
actionInstance = (Action) klass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return actionInstance;
}
protected Map defaultMap() {
Map map = new HashMap();
map.put("index", BootstrapAction.class);
map.put("addContactAction", AddContactAction.class);
map.put("removeContactAction", RemoveContactAction.class);
return map;
}
}
An ActionFactory is quite simple. It has a Map of action classes and their names.
We use the names in our pages to tell our servlet which actions to perform. In our
case, we have three actions:
• BootstrapAction
• AddContactAction
• RemoveContactAction
Recall that the actions to add and remove contacts were sent as URLs to our servlet
by the Add form and the Delete link, respectively. The BootstrapAction simply
When we tell the factory to create an Action, it instantiates the class and gives us
back the instance. Adding a new action to the factory is as simple as creating the
class for the action, then adding a new entry in the factory's action Map.
Actions
The Action interface looks like this:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface Action {
public String perform(HttpServletRequest request, HttpServletResponse response);
public void writeToResponseStream(HttpServletResponse response, String output);
}
The method we'll be using extensively now is perform(). The other method,
writeToReponseStream(), allows an action to write directly to the output stream
of the response, for passthrough to the JSP page. Whatever gets written (text,
HTML, etc.) shows up on the page. We won't be using that method for the moment,
but it's available on ContactsAction for you to see how it's done. Remember that
we used the code in the body of this method in our HelloWorldServlet, so it
shouldn't be unfamiliar to you.
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class BootstrapAction extends ContactsAction {
public String perform(HttpServletRequest request,
HttpServletResponse response) {
return "/" + "contactList.jsp";
}
}
Our action either returns a URL string or writes to the output stream for printing on
the JSP page. If our action returns a URL string, which our BootstrapAction
does, we get the ServletContext, ask it for a RequestDispatcher on our URL,
and finally forward the request and response to the JSP servlet so it can construct
the page. Our action servlet regains control after that, and can do any remaining
work, as long as it doesn't write to the JSP page's PrintStream, which is now
closed.
response.sendRedirect("http://...");
But there's a price to pay for doing that. When we use a dispatcher, we're delegating
the request and response to the JSP servlet, which also forwards the existing
HttpSession. That preserves the contents of the session. Forwarding to another
URL wouldn't. At the moment, when we're displaying the page initially, there's
nothing in the session that we care about, so the effect would be the same. But very
soon it will be important to preserve the session contents.
To tell our action framework about this new available action, we added this line to
the factory's action Map:
map.put("index", BootstrapAction.class);
Adding contacts
An app that shows a page that doesn't let you do anything isn't very useful. We need
to be able to add contacts.
To do that, we must:
Telling the factory about the action is as simple as adding another entry in the
factory's map, as we did with BootstrapAction.
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.roywmiller.contacts.model.Contact;
import com.roywmiller.contacts.model.ContactList;
public class AddContactAction extends ContactsAction {
public String perform(HttpServletRequest request, HttpServletResponse response) {
Contact newContact = createContact(request);
HttpSession session = request.getSession();
ContactList contacts = (ContactList) session.getAttribute("contacts");
contacts.addContact(newContact);
session.setAttribute("contacts", contacts);
return "/contactList.jsp";
}
protected Contact createContact(HttpServletRequest request) {
Contact contact = new Contact();
contact.setFirstname(request.getParameter(RequestParameters.FIRSTNAME));
contact.setLastname(request.getParameter(RequestParameters.LASTNAME));
contact.setStreet(request.getParameter(RequestParameters.STREET));
contact.setCity(request.getParameter(RequestParameters.CITY));
contact.setState(request.getParameter(RequestParameters.STATE));
contact.setZip(request.getParameter(RequestParameters.ZIP));
contact.setType(request.getParameter(RequestParameters.TYPE));
return contact;
}
}
All we do here is call createContact() to create a new Contact and set its
instance variables to the corresponding values containing the request parameters.
Then we add the new Contact to the ContactList in the HttpSession. Finally,
we tell our servlet to return to /contactList.jsp.
Recall that every time we create a Contact, the constructor assigns it a unique ID.
Look back at the JSP code for a moment. You'll see two important things in it with
regard to what we're doing in this action. First, notice that we guaranteed we'd
always have a ContactList instance in our session by adding this line:
When the JSP page is first compiled and displayed (as a result of forwarding to the
JSP page after BootstrapAction performs), it will instantiate a ContactList.
This object has nothing in it, which is why our list of contacts shows up empty when
we start the app. In AddContactAction, we're modifying that object to add new
contact information, then reinserting it in the session. When the page displays after
that, it will read the ContactList's list of Contact instances and display them.
Second, notice that our form to add contacts looks like this:
Removing contacts
Adding contacts is nice, but being able to remove them is just as important. Being
able to edit them would be nice, but this tutorial is only so long. Besides, adding edit
capability would be as simple as adding another action. So for now, we'll just add the
ability to remove contacts and then move on to more interesting things.
As before, all we have to do is add a new action class, implement its perform()
method, and tell the factory about it. We also have to be sure that our JSP code tells
our servlet to invoke the action at the right time.
Look at the Delete link for each row of the contacts table in our JSP page:
<a href="removeContactAction.perform?id=<%=
contact.getId()%>" >Delete</a>
That link tells our servlet to ask the factory for the right action class for the name
removeContactAction. It also passes a parameter called id in the request, with
a value set to the current contact's ID.
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.roywmiller.contacts.model.ContactList;
public class RemoveContactAction extends ContactsAction {
All we do here is grab the id parameter and the ContactList from the session,
tell the list to remove the Contact with that id, then replace the list on the session.
Last, but not least, we tell the servlet to forward back to contactList.jsp.
http://localhost:8080/contacts/
If Tomcat works its magic correctly, you should be looking at contactList.jsp, with no
contacts in the list. Type in some values for the text fields on the add form and click
the Add button. You should see a new contact in the list, complete with a Delete link
to the left of the contact's name. Unless you changed it, its type should be set to
Acquaintance (the default radio button selection for type). To keep things simple, we
didn't do any validation of the form, so you can enter multiple contacts with exactly
the same values for all fields. Each contact has a unique ID, so each one will show
up separately, and you can delete them individually.
That's it -- we have a working Web application! But we can't save our list of contacts,
so we have to reenter them every time we launch the app. Worse, every user of our
app has the same list of contacts. We can fix that by adding support for unique
users, and by storing data in a file (the simplest database that could possibly work).
We'll do both in the next section.
The ContactsUser
Our ContactsUser object looks like this, minus the import statements and
accessors (you can find the complete source code in contacts.jar):
public ContactsUser() {
}
public ContactsUser(String username, String password, List contactList) {
this.username = username;
this.password = password;
this.contactList.addAll(contactList);
}
public boolean hasContacts() {
return !contactList.isEmpty();
}
public void addContact(Contact aContact) {
contactList.add(aContact);
}
public void removeContact(Contact aContact) {
contactList.remove(aContact);
}
public void removeContact(int id) {
Contact toRemove = findContact(id);
contactList.remove(toRemove);
}
protected Contact findContact(int id) {
Contact found = null;
Iterator iterator = contactList.iterator();
while (iterator.hasNext()) {
Contact current = (Contact) iterator.next();
if (current.getId() == id)
found = current;
}
return found;
}
accessors...
}
This class holds information about users of the application. For the most part, that's
all it does. It holds the user's username and password, and maintains a list of
contacts for the user. It allows various actions in our action framework to add
Contacts to the user, and remove them. The no-argument constructor is there for
unit testing purposes. The other constructor, the one that takes three arguments, is
the one the app uses.
You might be asking yourself, "Why doesn't this class have a ContactList
instance variable?" After all, we went to the trouble of creating it in the first place.
Why don't we use it? The simple answer is that we really don't need that class
anymore. It wrapped an ArrayList and gave us some helper methods. Those
helper methods really make more sense on ContactUser. If we used a
ContactList, we'd be calling methods on it from ContactUser that have the
same names, and that we want to accomplish the same thing. For example, if
ContactUser had a ContactList, and we named that instance variable
contactList, here's what addContact() would look like:
Delegating to that other object here is a little silly. So we deleted the ContactList
class. That's what refactoring is all about. We simplified our code, and reduced the
number of classes in our system, but still got the same job done. Having
ContactList was an intermediate step in creating our system. It let us get up and
running, and helped us create our action framework. Then its useful life ended and
we got rid of it. Just because you wrote some code doesn't mean you have to keep it
around forever.
Changing contactList.jsp
Changing the JSP page to use our new ContactUser is straightforward. There are
three changes we need to make.
The second change is to update the table row-building logic in the page to use our
new user variable:
<%
List list = user.getContacts();
for (Iterator i = list.iterator(); i.hasNext();) {
Contact contact = (Contact)i.next();
%>
<a href="logoutAction.perform">Logout</a>
We'll put this link next to the "Contacts 1.0" header. When a user clicks the link, our
servlet will perform the LogoutAction.
simple. The only differences exist within the <body> tag. Here's login.jsp:
<body>
<h2>Contact List 1.0</h2>
<hr size="2"/>
<fieldset>
<legend><b>Please Login</b></legend>
<form method="post" action="loginAction.perform">
<table>
<tr>
<td>Username:<td>
<td><input type="text" size="30" name="username"></td>
</tr>
<tr>
<td>Password:<td>
<td><input type="text" size="30" name="password"></td>
</tr>
</table>
<br/>
<input type="submit" name="login" value=" Login ">
</form>
</fieldset>
</body>
This page has a form with two text fields and a submit button. When a user clicks
Login, our servlet will perform the LoginAction.
Here's goodbye.jsp:
<body>
<jsp:useBean id="user" scope="session"
class="com.roywmiller.contacts.model.ContactsUser"/>
<h2>Contact List 1.0</h2>
<hr size="2"/>
Goodbye <%= user.getUsername() %>!
</body>
When a user tries to log in with a username and password that aren't in our
database, our application gives up and routes the user to an error page, which looks
like this:
<body>
<h2>Contact List 1.0</h2>
<hr size="2"/>
<fieldset>
<legend><b>Error</b></legend>
There was an error: <%= session.getAttribute("errorMessage") %>
</fieldset>
</body>
This is the simplest page we have. It uses the default session variable available in
all JSP pages to display an error message.
Adding LoginAction
The LoginAction class looks like this:
This action grabs the username and password parameters from the request, then
checks to see if our database contains a user with that username/password
combination. If it does, we put the user in the session and go directly to
contactList.jsp. If that user doesn't exist in our database, we set an error message
on the session and go to error.jsp.
Adding actions should be easy for us now. We add an entry to our action factory that
looks like this:
map.put("loginAction", LoginAction.class);
With the pages in place, and our factory aware of our new action, we're done. You
should be able to run the app and see the login page. When you enter a username
and password, regardless of what they are, you should see the error page. In just a
little while, you'll be able to log in with a valid username and password, and see
contactList.jsp with an empty contact list.
Adding LogoutAction
The LogoutAction class looks like this:
shutDown() calls writeUsers(), which iterates over all the users we have in
memory (from when we read in the file when the servlet initialized itself), creates a
UserRecord for each, then passes the complete string to writeText(). That
method dumps the string to the file, overwriting the existing contents. The
UserRecord class is a nice helper that encapsulates all of the somewhat messy
tokenizing work for each user record in the file. You can examine the code for
yourself (see contacts.jar for the complete source code listing).
Once the database is shut down, we tell the servlet to forward to goodbye.jsp to
display our personalized goodbye.
industrial-strength RDBMS, but a text file can be a database also. It's the simplest
database that could possibly work. If you wrap it well, and hide the access details
behind an interface that makes accessing the data reasonably painless for other
classes in your app, the form of the underlying data store really doesn't matter.
In our application, we'll use a text file. That file will contain a single line per user, in
the following form:
username password
comma-delimited contact1 info|comma-delimited contactN info|...
The usernames in our file will be plain text, but the passwords will be Base64
encoded for (arguably too-simple) security. The entries for contacts will be
comma-delimited within each. The contacts themselves will be delimited by pipe
characters (|). There's no magic to this formatting. It just does what we need it to do,
which is to allow us to parse the file easily.
For convenience, we'll put this file at the root of our project so that the path to the file
is straightforward.
To keep things simple, our application doesn't support user maintenance features,
which means that there's no way to add or remove a user from within the app. That
means that you have to add users to userDatabase.txt manually. For example, to
add a user named testuser with the password password, add the following line
to the file:
testuser cGFzc3dvcmQ=
The password in each entry is encoded with Base64 encoding. You can use the
EncoderDecoder class in contacts.jar to figure out the encoded version of your
password. Its main() method lets you enter a plaintext string, then run the class to
print the encoded password to the console.
The UserDatabase
Our UserDatabase wraps interaction with our text file. The class listing looks large,
but it's not complicated (much of the perceived complexity is the extra Java coding
stuff you need to deal with reading and writing files). We'll discuss a few high points
in this section (see contacts.jarfor the complete code listing).
The class implements the Singleton pattern. The class maintains a single instance
that all users share by calling getSingleton().
and password acting as the key for each entry. Anything could serve as the key, but
this was convenient.
In our servlet init() method, we'll tell the UserDatabase where the database file
is located (based on the ServletContext), then we'll tell it to initialize itself by
calling initialize(). That method looks like this:
This method reads in the complete file by calling retrieveText, tokenizes the big
string, creates a UserRecord for each user, then calls put() to place the new
ContactsUser in the map. The real work of this method goes on in the calls to
retrieveText() and put():
The put() method creates the key for the user (the username plus the password)
and inserts it in the map of users.
Introduction
In this tutorial, we've only scratched the surface of what you can do with servlets.
Web apps can be as sophisticated as you can imagine. The underlying mechanisms,
though, are essentially the same for all of them, and if you're writing code in the Java
language, servlets are at the core. Creating more sophisticated apps is a matter of
using more sophisticated tools and libraries.
However, that's where many programmers make mistakes that lead them to create
terrible Web applications. This section contains some suggestions on how to avoid
that. Most Java programmers with Web development experience would agree with
some of these. Some are more controversial. In any case, they should help get you
started with servlets well.
Servlets aren't a place for business logic. That's bad OOP design. Think of a servlet
as one of two things:
• An extra layer behind your UI that helps get "events" to the server
• An extra layer in front of the server that allows you to use a browser as
your UI
Either way, it's a place where you quickly dispatch matters to other pieces of your
application, then get out.
Use actions
An action framework, even a simple one like the one we used in this tutorial, is a
powerful approach. It allows you to follow my previous bit of advice: to spend as little
time in your servlet as possible. It's also good OOP design. Each action class does
one thing, or a very cohesive set of related things.
Some people think this fragments the code, making it more difficult to understand. I
think that this objection stems from two things:
I don't know where these ideas come from. Bruce Eckle suggests that it's a holdover
from the CGI days, when folks grew used to paying attention to whether a GET or
POST was coming in. (See Resources for a link to a more detailed version of his
theory.) I've never heard a good reason not to use service(). In any case, if you
use it, and then determine later that it would be better to use one of the doX()
flavors, refactor the code! Until then, use service(), because it's easier.
It's wiser to keep presentation in the place where it belongs: in the page. JSP
technology allows you to do that, but as I said earlier, it requires a lot more work to
keep business logic and presentation separate. Templating engines like Velocity are
often a better choice. Whatever approach you choose, minimize the mixing of
business logic and presentation.
Many of the packaged Web app development libraries, like Struts (see Resources),
come with built-in frameworks for handling dynamic display of messages, including
errors. Use those features.
Use a framework when it's wise to use one. Don't assume that you need it. Wait for
your system to tell you that you do. Some programmers consider that "bad design."
Not so. Assuming you'll need a particular framework, or even a particular feature of
that framework, can be overdesign. You should design for what you need; most
often, that changes as the system grows. It's frustrating to pick a framework before
development begins, then hit a brick wall because that framework doesn't support or
allow something you want to do.
Section 7. Summary
Conclusion
In this tutorial, you've learned about Java servlets, and how to use them
professionally. Certainly the examples in this tutorial are simple, but they illustrate
most of the servlet concepts you would use to create a Web application. There are
more features (configuration and otherwise) available to you, but the core of almost
every Web app written with the Java language is one or more servlets acting as
gatekeepers for business logic on one or more servers behind the scenes.
More importantly, you've learned some techniques for using servlets well. Web
applications often grow into messy piles of code. By using some simple techniques
driven by fundamental OOP principles, you can avoid the mess, and create apps
that are a joy to enhance and maintain.
Resources
Learn
• David Geary's Advanced JavaServer Pages (Pearson Higher Education, 2001)
is a must-read.
• If your Web application is too complex for simple JSP components, you may
wish to investigate the Velocity Template Engine or the Struts Web Application
Framework. Both projects are hosted by the Apache Foundation.
• The Java Technology home page is the "official" Java language resource. The
Java Servlet Technology section of the site offers a wealth of information on
programming with servlets.
• The Java tutorial from Sun is an excellent resource. There is a "trail" for
servlets.
• The developerWorks New to Java technology page is a clearinghouse for
developerWorks resources for beginning Java developers, including links to
tutorials and certification resources.
• Bruce Eckle speculates on historical reasons why many Web programmers are
reluctant to override service().
• You'll find articles about every aspect of Java programming in the
developerWorks Java technology zone.
• Also see the Java technology zone tutorials page for a complete listing of free
Java-focused tutorials from developerWorks.
Get products and technologies
• Download the contacts.jar that accompanies this tutorial.
• Download Tomcat from the Apache Jakarta Project.
• You'll also need the Tomcat plug-in for Eclipse from Sysdeo. Once you've
installed it, check out the HelloWorld servlet setup "tutorial" from Sysdeo.
Discuss
• Participate in developerWorks blogs and get involved in the developerWorks
community.
He began his career at Andersen Consulting (now Accenture), and most recently
spent three years using Java professionally at RoleModel Software, Inc. in Holly
Springs, NC. He has developed software, managed teams, and coached other
programmers at clients ranging from two-person start-ups to Fortune 50 companies.