Untangle your servlet code with reflection

Simplify servlet structure by breaking down functionality into separate methods

Despite the elegance of the Servlet API, real-world servlet classes tend to have pages of code in their doGet() and doPost() methods, often with complex conditional logic. Over time, more code must be added in response to evolving business needs. Long methods with complex branching result in a major headache for the unlucky developer who must maintain that code. Good programming style suggests that such branching should be avoided.

This article presents a technique to modularize the functionality of a complex servlet using reflection, thereby simplifying the servlet structure. Specifically, the CTLServlet base class parses the request URL to determine the name of an appropriate handler, finds a method by that name in the implementation class, and then invokes the method to handle the request. That makes it easy to break down servlet functionality into separate methods, and thus avoid very long method bodies within doGet() and doPost().

As I will describe in more detail, this technique has several other important advantages. It replaces long conditional dispatching code with a simple lookup. It encourages a disciplined relationship between URLs and handler methods, which makes it easier to analyze and extend servlet behavior. It is very lightweight, and thus imposes little burden upon developers. And it readily supports a number of interesting extensions, such as trace functionality for debugging, and a more object-oriented style.

About reflection

The Reflection API provides a way to discover what an object can do at runtime and to execute the discovered capabilities. As Michael T. Portwood, a presenter at JavaOne, put it, reflection can make applications more “simple, flexible, and pluggable” without sacrificing performance or increasing overall program complexity. The approach used in this article parallels a technique described in that JavaOne presentation (see Resources). This technique uses reflection to implement a generic ActionListener in Swing or AWT code (see Resources). The reflection-based code is much shorter and simpler than its conditional alternative.

For example, the conventional conditional approach compares the ActionEvent‘s command to a set of known strings and then branches to an appropriate handler method, like this:

public void actionPerformed(ActionEvent ev) {
   String command = ev.getActionCommand();
   if (command.equals("start") {
      start();
   }
   else if (command.equals("stop")) {
      stop();
   }
   else if (command.equals("another")) {
     another();
   }
}

Notice that you dispatch to a method that matches the name of the action command in the ActionEvent object. Reflection can capture this dispatching pattern, as follows:

public void actionPerformed(ActionEvent ev) {
  try {
         Method m = getClass().getMethod(getActionCommand(), new Class[0]);
         m.invoke(this,new Object[0]);
  }
  catch (Exception ex) {
  
  }
}

Now you can easily add new command handlers, simply by adding new zero-argument methods to your class — without ever updating the dispatching code.

The extension of this idea to servlets is pretty straightforward, although surprisingly powerful. Instead of finding the name of a command in an ActionEvent object, you find the name in a component of the request URL. Instead of calling a zero-argument method with that name, you call a method that takes two arguments, one of type HttpServletRequest and the other of HttpServletResponse. With that approach, you can break up the pages of code in your doGet() and doPost() methods into many smaller well-named methods; eliminate complex dispatching code; and make it much easier to add new functionality to an existing servlet.

The part of the Reflection API you need to understand to implement this approach is pretty concise. You already know that all Java objects are instances of a class. Reflection introduces a special class called Class to represent an instance of class. You can get a Class object by calling instance.getClass(). Suppose you have an object, o, that is an instance of a servlet. You can get its class as:

o.getClass();

The Class class has a number of methods to return information about a class, such as its name, its fields, and, of course, its methods. The latter are represented by a Method object, which contains information about a method. You can get a particular method of a class by calling getMethod() with an array of Classes, one for each formal argument to the method. Support your o object with a doSomething(String s) method. You can get this method by calling:

o.getClass().getMethod("doSomething",new Class[] {String.class});

Once you have the method, you can invoke it by calling the Method object’s invoke method. For example, you can invoke doSomething on o with the argument "hello" by calling:

o.getClass().getMethod("doSomething",new Class[] {String.class}).
   invoke(o,new Object[] {"hello"});

Except for some exception-handling details, that is all you need to know about reflection for this article.

Model-view-controller architecture

I will elaborate the use of reflection within the model-view-controller (MVC) architecture, which is recommended as a server-side Java architecture. Detailed coverage of MVC’s application to servlets can be found in Resources. Important to this discussion is organizing your request-processing code into three categories:

  1. State information (models) will be coded as JavaBeans and stored in the user’s session.
  2. Code to process the request and update the state (controllers) will be written as methods of the servlet class.
  3. Code to format the response and return it to the client (views) will be written as JavaServer Pages (JSPs), which render the updated state into HTML.

Therefore, methods that handle specific requests will hereafter be called controllers. Later in the article, you will see that you can extend the reflection concept into a more object-oriented design that leverages the ability of state information to be stored as beans in the user’s session.

Disciplining URLs

In addition to MVC, another important idea is that request URLs to a servlet should follow a disciplined pattern. This is not a new idea: the base URL for your servlet already follows a pattern, either or a more convenient alias like But then you are free to improvise, either by adding extra path information or request parameters. Extra path information is any text after the servlet name but before the “?” that marks the beginning of the request parameters. The request parameters are the name-value pairs that follow the “?” symbol in the URL. For example, in the URL “/YourServlet/extra/path/information?p1=1&p2=2” the extra path information would be “/extra/path/information” and the request parameters are “p1” and “p2.”

As more functionality is added to the servlet, there is a tendency to use an increasing number of arbitrarily named parameters in combination with path information to specify servlet functions. You should impose some discipline on this chaos. Indeed for a more automated approach to dispatching, it is essential to settle on some additional conventions.

In this article, I’ll require the first piece of extra path information to be the name of the major function being called and allow the parameters to continue to vary arbitrarily. Hence, the URL “/Calculator/add?x=1&y=2” would invoke the add function of the Calculator servlet, whereas “/Calculator/subtract?x=1&y=2” would invoke the subtract function. (An equally workable alternative is to use a specially named parameter, such as “fn” to contain the function name, in URLs such as “/Calculator?fn=add&x=1&y=2” or “/Calculator?fn=subtract&x=1&y=2.”

Dispatching via reflection using CTLServlet

Now I’ll introduce your base class, CTLServlet. This base class will implement doGet() and doPost(); deployable servlets will be subclasses that add controller methods. The CTLServlet class is abstract, because concrete subclasses will have to implement at least two methods. The first, handleRequest(), will be called by default, if a URL invokes the servlet without specifying a particular controller. The second, handleControllerNotFound(), will be invoked if a URL invokes the servlet and specifies a particular controller, but that controller cannot be found. In addition, concrete subclasses will add one or more controller methods.

abstract public class CTLServlet extends HttpServlet {
      public void doGet(HttpServletRequest request, HttpServletResponse response) {
            doGetOrPost(request, response);
      }
      public void doPost(HttpServletRequest request, HttpServletResponse response) {
            doGetOrPost(request,response);
      }

You can handle HTTP GET and HTTP POST requests identically, each implementation delegating to a single doGetOrPost() method. This method dispatches the request to a controller. The controller is found by extracting a method name from the request. If no controller name is found, a default method, handleRequest, is called. To make sure the concrete class has such a method with the right signature, you declare it as abstract. You also declare the abstract method handleControllerNotFound(), which is called if the request specifies a controller but a corresponding method doesn’t exist.

You will also note that doGetOrPost() adds a servletPath attribute to the request. This is because JSPs often need this information to correctly compute URLs to call back to the servlet. It also illustrates a general point about this approach. Even though your functionality will be modularized into different methods, every call through your servlet goes through a single “bottleneck” method. This method, doGetOrPost, can hold the common code that the controller needs.

      private void doGetOrPost(HttpServletRequest request, HttpServletResponse response) {
            try {
                  request.setAttribute("servletPath",request.getServletPath());
                  dispatch( getController(this,getMethodName(request)),
                          this,
                          request,
                          response);
            }
            catch (ControllerNotFoundException ex) {
                  handleControllerNotFound(request, response);
            }     
        }
        abstract protected void handleControllerNotFound(HttpServletRequest request, HttpServletResponse response);
  
        abstract public void handleRequest(HttpServletRequest request, HttpServletResponse response);

The dispatch() method simply invokes the method on the target object. It throws a ControllerNotFoundException should anything go wrong.

      protected void dispatch(Method m, 
                                          Object target, 
                                          HttpServletRequest request, 
                                          HttpServletResponse response) 
                                    throws ControllerNotFoundException {
          try {
            m.invoke(target,new Object[] {request,response});
          } catch (IllegalAccessException ex) {
            throw new ControllerNotFoundException("couldn't access method");
      } catch (InvocationTargetException ex) {
              throw new ControllerNotFoundException("object doesn't have method");
      }
      }

The getController() method finds a named controller using reflection. If a method name is not supplied in the URL, the handleRequest() method is returned. This abstract method must be implemented in the concrete servlet and should provide appropriate default behavior (for example, showing a home page).

      static final Class[] sFormalArgs = {HttpServletRequest.class,HttpServletResponse.class};
      protected Method getController(Object target, String methodName) 
                  throws ControllerNotFoundException {
            try {
                  return target.getClass().getMethod(methodName,sFormalArgs);
            }
            catch(NoSuchMethodException ex) {
                  throw new ControllerNotFoundException("couldn't get method");
            }
      }
   }

The last method of CTLServlet, getMethodName(), parses the extra path information in a request URL to find a method name. You do this by using a StringTokenizer and finding the first token of the extra path information, assuming that “/” delimits tokens:

  protected String getMethodName(HttpServletRequest req) {
      String defaultMethod = "handleRequest";
      if (req.getPathInfo() == null) return defaultMethod;
       
       
      StringTokenizer tokens = new StringTokenizer(req.getPathInfo(),"/");
      if (tokens.hasMoreTokens()) {
          return tokens.nextToken();
      }     
      else return defaultMethod;
  }

Finally you define your own class of exception, which is used internally to collapse all the possible reflection exceptions into one; you shouldn’t care why reflection failed, and you can report in all cases “controller not found.” The exception text will contain the detailed reason for the failure:

  class ControllerNotFoundException extends ServletException {
  
            ControllerNotFoundException(String reason) {
                  super(reason);
            }
             
  }

That is the end of CTLServlet. Note that while a concrete servlet must override at least handleControllerNotFound() and handleRequest methods, it also can override two additional methods. The first method, getController(), locates a controller method with a given name. The provided implementation finds the method via reflection, although a subclass could find it some other way (e.g., in a lookup table). The second method, getMethodName(), finds a controller name within a URL. The provided implementation finds the name in the extra path information, although a subclass could find it some other way (e.g., from the value of a particular parameter).

A bookmark servlet example

To see how CTLServlet works, you will implement a Bookmark servlet. This servlet will keep a list of bookmarks for the user. The servlet functions will be to add a new URL, edit an existing URL, or delete an existing URL. In many ways, this will be a toy example, because you will not deal with storing and retrieving bookmarks from a database or structuring a bookmark collection into folders.

As mentioned earlier, this implementation will follow an MVC architecture. For the Bookmark servlet, the state consists of two lists of bookmarks, one private and the other public. These are the models, and they will be implemented by a class that follows JavaBeans conventions. The views consist of a list view that shows all the bookmarks available and a detail view for entering and editing bookmarks. These will each be implemented as JSPs. The controllers will consist of the functions you can call: add, delete, or edit a particular book, or just view the list. In summary, you will have to write:

  • BookmarkServlet class, which extends CTLServlet and implements methods called add, delete, edit, and list
  • BookmarkList class, which contains a list Bookmarks
  • BookmarkData class, which contains a URL and a bookmark name

You’ll also use a trivial BookmarkData class. Note I’ve included a constructor that takes no arguments. This is a requirement for your class to be usable in a JSP useBean tag.

public class BookmarkData {
  public String url;
  public String name;
  
  public BookmarkData() {
      url = name = "";
  }
  
  public BookmarkData(String url, String name) {
      this.url = url;
      this.name = name;
  }     
 }

The BookmarkList class has a vector for the bookmarks and the basic operations you need:

public class BookmarkList {
      Vector mList = new Vector();
      String mName = "";
      public BookmarkList() {
          
      }
      public BookmarkList(String name) {
            mName= name;
      }
      public Enumeration getBookmarks() {
            return mList.elements();
      }
      public void add(String url, String name) {
            mList.addElement(new BookmarkData(url, name));
      }
      public void remove (String name) {
            mList.removeElement(find(name));
      }
      public void change(String oldName, String newname, String newurl) {
         BookmarkData data = find(oldName);
         if (data == null) return;
          
         data.url = newurl;
         data.name = newname;
      }
      BookmarkData find(String name) {
         Enumeration en = getBookmarks();
         while (en.hasMoreElements()) {
            BookmarkData data = (BookmarkData)en.nextElement();
            if (data.name.equals(name)) return data;
         }
         return null;
      }
}

Now you can easily write your BookmarkServlet class. Note that it will have one method for each controller: add, list, remove, and edit. These will map onto URLs as follows:

  • To add a bookmark: /BookmarkServlet/add?name=Yahoo&url=http://www.yahoo.com
  • To remove a bookmark: /BookmarkServlet/remove?name=Yahoo
  • To show the form to edit a bookmark: /BookmarkServlet/edit?name=Yahoo
  • To submit the changes from editing a bookmark: /BookmarkServlet/edit?name=Yahoo&newname=Yahoo&newurl=http://yahoo.com
  • To view the bookmarks: /BookmarkServlet

Here is the code:

public class BookmarkServlet extends CTLServlet {
  
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
            show(request, response);
    }
   public void show( HttpServletRequest request, HttpServletResponse response) {
      viewList(bookmarkList(request), request,response);
   }
   public void add(HttpServletRequest request, HttpServletResponse response) {
      BookmarkList list = bookmarkList(request);
      list.add(request.getParameter("url"),request.getParameter("name"));
      request.setAttribute("message","added 1 new bookmark");
      viewList(list,request,response);
   }
   public void remove(HttpServletRequest request, HttpServletResponse response) {
      bookmarkList(request).remove(request.getParameter("name"));
      request.setAttribute("message","removed 1 bookmark");
      viewList(bookmarkList(request),request,response);
   }
   
   public void edit( HttpServletRequest request, HttpServletResponse response) {
      String oldname = request.getParameter("oldname"),
               newname = request.getParameter("name"),
               newurl = request.getParameter("url");
               
      BookmarkList list = bookmarkList(request);
               
      if (newname == null) {
         if (oldname != null) {
            viewEditForm(list,oldname,request,response);
         }
         else viewList(list,request,response);
      } 
      else {
         list.change(oldname,newname,newurl);
         request.setAttribute("message","saved changes to 1 bookmark");
         viewList(list,request,response);
      }
   }
   
   BookmarkList bookmarkList(HttpServletRequest request ) {
       return (BookmarkList)getValidSession(request).getValue("bookmarks");
   }
   
   HttpSession getValidSession(HttpServletRequest request) {
       HttpSession session = request.getSession(true);
       if (session.getValue("bookmarks") == null) {
            session.putValue("bookmarks",new BookmarkList("public"));
       }
       return session;
   }
   
   void viewList(     BookmarkList list, 
                              HttpServletRequest request, 
                              HttpServletResponse response) {
            try {
                  request.setAttribute("list",list);
                  request.setAttribute("mode","view");                  
                  RequestDispatcher dispatcher 
                        = getServletContext().getRequestDispatcher("/javaworld/bookmarks.jsp");
                  dispatcher.forward(request,response);
            } catch (ServletException ex) {
             
            } catch (IOException ex) {
             
            } catch (NullPointerException ex) {
             
            }
   }
   
   void viewEditForm(BookmarkList list, 
                               String name,
                               HttpServletRequest request, 
                               HttpServletResponse response) {
            try {
                  request.setAttribute("list",list);
                  request.setAttribute("mode","edit"); 
                  request.setAttribute("bookmark",list.find(name));                 
                  RequestDispatcher dispatcher 
                        = getServletContext().getRequestDispatcher("/javaworld/bookmarks.jsp");
                  dispatcher.forward(request,response);
            } catch (ServletException ex) {
             
            } catch (IOException ex) {
             
            } catch (NullPointerException ex) {
             
            }
   
   }
   
   protected void handleControllerNotFound(     
                  HttpServletRequest request, 
                  HttpServletResponse response) {
            show(request, response);
      }
}

In this code, each of the main controller methods extracts appropriate parameters from the request and calls the appropriate operation on the bookmark list. In a real-world example, you’d also validate the parameters, but I’ve left that code out for now. There are also three utility methods. The getValidSession() method ensures that you have an initialized session. In the real world, the getValidSession() method would use the client’s login information to load the session with data from a database. In this case, you just create an empty BookmarkList in the user’s session. The other two methods, viewList and viewEditForm, dispatch to the JSPs used. For simplicity, I used a single JSP to present two different views, depending on a "mode" attribute that is passed into the servlet from the request. The mode is either "view" or "edit" That JSP is displayed below:

<html>
<head>
      <title>My Bookmarks</title>
      <meta name="generator" content="BBEdit 6.0">
      <%@ page import="javaworld.*" import="java.util.*" %>
      <jsp:useBean id="message" scope="request" class="String" />
      <jsp:useBean id="mode" scope="request" class="String" />
      <jsp:useBean id="bookmark" scope="request" class="BookmarkData" />
      <jsp:useBean id="list" scope="request" class="BookmarkList" />
      <jsp:useBean id="servletPath" scope="request" class="String" />
</head>
<body bgcolor="#FFFFFF">
<h2>My Bookmark Collection</h2>
<% if (message.length() > 0) { %>
<p style="color: red"><%= message %>
<% } 
   if (! mode.equals("edit")) { 
%>
<table class="legacyTable" border="1" cellpadding="3">
<% Enumeration en = list.getBookmarks();
   while(en.hasMoreElements()) {
      BookmarkData data = (BookmarkData)en.nextElement();
%>
      <tr>
            <td><%= data.name %></td>
            <td><%= data.url %></td>
            <td><a href=" servletPath %>/edit?oldname=<%= data.name %>">edit</a>
               <a href=" servletPath %>/remove?name=<%= data.name %>">remove</a>
            </td>
      </tr>
<% } %>
</table>
<hr>
<form action="<%= servletPath %>/add" method="post">
name: <input type="text" name="name" size="12" maxlength="12">
url: <input type="text" name="url" size="60">
<input type="submit" name="submit" value="add">
</form>
<% } else { %>
<form action="<%= servletPath %>/edit" method="post">
<input type="hidden" name="oldname" value="<%= bookmark.name %>">

Editing <%= bookmark.name %>

<input type="text" name="name" value="<%= bookmark.name %>" size="12"> <input type="text" name="url" value="<%= bookmark.url%>" size = "60"> <input type="submit" name="submit" value="submit"> </form> <% } %> </body> </html>

The JSP starts out by getting access to a number of parameters passed to it through the request via the useBean tag. The list bean will be the BookmarkList to be formatted. By passing this bean in the request, you can reuse the JSP to format a number of bookmark lists (if the user had separate public and private lists, for example). The bookmark bean is used only during editing, and it contains the data of the bookmark being edited. The message bean, if present, is used to show a status message to the user. The servletPath bean, really just a string, contains the path (such as "mybookmarks") sufficient to invoke this servlet. By using this to create URLs, you can create a JSP that is portable regardless of how the servlet is configured on the server. For example, in the above JSP, the href attribute of the links is constructed by combining the servletPath and the specific action, e.g., a href=" servletPath %>/remove?name=<%= data.name %>". For example, on some servers you might call the servlet as "/servlet/BookmarkServlet" while on others that support aliases for servlets you might use "/mybookmarks".

In view mode, the servlet lists the bookmarks in a table, with links to allow clicking, editing, or removing each bookmark. In view mode, you also provide a form for adding additional bookmarks. In edit mode, you present an existing bookmark in a form and let the user make changes. Where you see the code "/edit" or "/remove" in this JSP, a link is being created that calls the servlet back, with either edit or remove as the method being called.

Extending to an object-oriented style

You can also extend CTLServlet to an object-oriented style, whereby you parse the URL to obtain not only a method name but also an object name. You then call the method on that object. Why bother?

Object-oriented programming suggests it is good to bundle methods with the data on which they act. It also gives considerably more flexibility. For example, BookmarkServlet could have two different bookmark lists, one for public consumption and one private to the owner. Each could be represented by an instance of the same class and called through URLs such as “/mybookmarks/public/add” and “mybookmarks/private/add.”

To implement this strategy, you need to make only two changes. First, you extend CTLServlet to its object-oriented subclass CTLServletOO. That will dispatch to a controller method on a particular object. In particular you will look for URLs of the form “/servlet/object/method?” and dispatch to the method named on the object. You will find the object by looking for something with that name in the session (although another implementation could look somewhere else, such as in a database). Second, you need to move the methods such as add, edit, remove, and show from BookmarkServlet to BookmarkList.

The following doGetOrPost method overrides the doGetOrPost method in CTLServlet and parses the path information to determine both an object and a method name to call. Because this parsing is a bit tricky, you can use an inner helper class, ParsedPathInfo, to do the work.

protected void doGetOrPost(HttpServletRequest request, HttpServletResponse response) {
      try {
            ParsedPathInfo ppi = new ParsedPathInfo(request);
            dispatch( getController(ppi.object,ppi.methodName),
                    ppi.object,
                    request,
                    response);
      }
      catch (ControllerNotFoundException ex) {
            handleControllerNotFound(request, response);
      }     
}

The ParsedPathInfo class takes apart the path info via a StringTokenizer. There may be no extra path information (e.g., "/mybookmarks/"), only a method name (e.g., "/mybookmarks/add"), or a method and object name (e.g., "/mybookmarks/public/add"). In the first case, you can dispatch to a default object and a default method (handleRequest) . The default object is the servlet itself, although subclasses can override behavior by defining their own getDefaultObject() method. In the second case, you can dispatch to the named method on the default object (this maintains compatibility with the original version of CTLServlet). In the final, object-oriented case, you can look for an object with the given name in the user’s session. If you find it, you will dispatch to the named method on that object.

      public class ParsedPathInfo {
            String      methodName;
            Object  object;
            private String  firstPart, secondPart;
            public ParsedPathInfo(HttpServletRequest request)
                  throws CTLServlet.ControllerNotFoundException {
               
                  String pathInfo = normalize(request.getPathInfo());          
                  parsePath(pathInfo);
          
                  if (secondPart == null && firstPart == null) {
                        object = getDefaultObject();
                        methodName = "handleRequest";
                  }
                  else if (secondPart == null) {
                        object = getDefaultObject();
                        methodName = firstPart;
                  }
                  else {
                        object = request.getSession().getValue(firstPart);
                        methodName = secondPart;
                        if (object == null) throw new ControllerNotFoundException("missing object");
                  }
               
            }
          
            private String normalize(String pathInfo) {
                  return (pathInfo != null) ? pathInfo : "/";
          
            }
          
            private void parsePath(String pathInfo) {
                    
                  StringTokenizer tokens =
                        new StringTokenizer(pathInfo,"/");
                  if (tokens.hasMoreTokens()) {
                        firstPart = tokens.nextToken();
                  }
                  if (tokens.hasMoreTokens()) {
                        secondPart = tokens.nextToken();
                  }
            }
     }

I do not provide an implementation of the BookmarkServlet in the object-oriented style; instead I’ve left that as an exercise for the reader. The first step, however, would be to move the controller methods from the servlet to the BookmarkList object (and to override getDefaultObject() so that the BookmarkList in the session is returned). When moving the method, you carry out the operation on this instead of referring to an external bookmark list. Other than that, the method is the same:

   public void add(HttpServletRequest request, HttpServletResponse response) {
      BookmarkList list = this;
      list.add(request.getParameter("url"),request.getParameter("name"));
      request.setAttribute("message","added 1 new bookmark");
      viewList(list,request,response);
   }

Extending CTLServlet to trace method calls

One nice feature of centralized dispatching is that it also makes it easy to centralize trace and other debugging functionality. Since every URL is dispatched through CTLServlet‘s dispatch method, you can easily extend dispatch to print debugging information. In that case, you can modify dispatch as follows:

 protected void dispatch(Method m, Object target, HttpServletRequest request,
 HttpServletResponse response) {
    if (m == null) {
      handleControllerNotFound(request, response);
      return;
    }
    try {
     if (mDebug) System.out.println("entering " + m.getName());
      m.invoke(target,new Object[] {request,response});
     if (mDebug) System.out.println("exiting" + m.getName());
    } catch () {
         if (mDebug) System.out.println("an exception was thrown by" + m.getName()); 
         ex.printStackTrace();
    }
     
 }

Adding new functionality

Another nice feature of this architecture is how easily you can add new functionality without disturbing the status quo. Suppose you decide you want to be able to turn tracing on or off with a URL. Piece of cake! You just add two methods to your servlet, and call them via /mybookmarks/traceon and /mybookmarks/traceoff:

public void traceon(HttpServletRequest request, HttpServletResponse response) {
  mDebug = true;
  handleRequest(request, response);
}
public void traceoff(HttpServletRequest request, HttpServletResponse response) {
  mDebug = false;
  handleRequest(request, response);
}

Running the code

In the source code, I provide tests written with JUnit (see JavaWorld article about JUnit in Resources below) that can be run without a servlet engine. These tests confirm the proper behavior of the CTLServlet and Bookmark classes. I have found it very productive to write tests routinely as part of developing servlets, although the hows and whys of using JUnit with servlets would require an article unto itself. At any rate, if you fire up java junit.ui.TestRunner javaworld.TestCTLServlet with an appropriate classpath, you can run the tests. The tests serve as further documentation of how dispatching via reflection should work.

To actually interact with the servlet, you’ll need a servlet engine. I use the free ServletExecDebugger (see Resources), which includes its own integrated Web server and integrates well with the CodeWarrior debugger. When you deploy the BookmarkServlet with your servlet engine, you’ll have to map the classname to an appropriate alias and move the JSP into a directory named “javaworld” within the root directory of your Web server.

Conclusion

This article presented a technique to modularize the functionality of a complex servlet using reflection, which simplifies the servlet structure. In particular, you learned to parse the incoming URL and then to use reflection to find an appropriate named method to handle the request. You then extended this technique to parse for both an object and a method and to dispatch to the named method on the object.

This particular parser found the information in the URL’s extra path info and found the object in the session. But this same technique could equally well be used with different parsing and lookup techniques. The name of the method and object could just as easily be specially named parameters of the request. And you could locate the target object in a database, via RMI, in the ServletContext, in a hashtable stored in a static variable, or anywhere you can get an object from a name.

The reflection technique’s most important advantage is that it modularizes request-handling functionality into methods and eliminates long, complex conditionals in the doGet() and doPost() methods. Further, it accomplishes those things without imposing much of a learning curve on developers. Developers already understand organizing functionality into methods; this technique asks you to use that knowledge to make handler methods that take a request and response. It is further extensible to a straightforward object-oriented paradigm. Finally, this technique has the additional benefits of imposing more discipline on the relationship between URLs and source code and making it easy to add centralized debugging code in the bottleneck dispatch() method. Both those benefits make debugging and maintenance of servlet code easier.

Jeremy Roschelle is a senior cognitive
scientist and project lead at SRI International’s Center for
Technology and Learning, where he has developed state-of-the-art
learning-oriented Websites using servlets and JSPs. Jeremy has
published more than 40 articles and is frequently invited to speak
nationally and internationally on the application of technology to
education. SRI, a nonprofit center of innovation in Silicon Valley,
develops research-based solutions to important problems in
education, health, computer science, materials, and life sciences.

Source: www.infoworld.com