ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Servlet Best Practices, Part 1
Pages: 1, 2, 3, 4

High-profile frameworks

While it would be wonderful to do a full comparison between frameworks here, that's not what this book is about. What we can do instead is briefly discuss the four most popular servlet frameworks available today: Java 2 Enterprise Edition (J2EE) BluePrints, Apache Struts, JavaServer Faces, and Apache Turbine.



You might be thinking to yourself, "Just skip the summary and tell me which is best!" Unfortunately, there's no all-encompassing answer; it depends entirely on your application and personal taste. This is one place where working with server-side Java follows Perl's slogan: "There's more than one way to do it."

J2EE BluePrints
J2EE BluePrints is more accurately described as a guidebook than a framework. Authored by Sun engineers, the book provides guidelines, patterns, and code samples showing how best to use J2EE and the constituent technologies. For example, the book shows how to implement a Model-View-Controller (MVC) framework that encapsulates back-end web operations into three parts: a model representing the central data, the view handling the display of the data, and the controller handling the alteration of the data. To support this MVC model BluePrints suggests using an Action class in the style of the "Command" pattern:

The sample application defines an abstract class Action, which represents a single application model operation. A controller can look up concrete Action subclasses by name and delegate requests to them.

The book gives code samples for how to implement an Action but doesn't provide any production-quality support code. For production code, the J2EE BluePrints book points readers to Apache Struts.

Apache Struts
Apache Struts might very well be the most popular servlet framework. It follows very closely the MVC pattern discussed in BluePrints (from what I can tell, the ideas have flowed in both directions):

Struts is highly configurable, and has a large (and growing) feature list, including a Front Controller, action classes and mappings, utility classes for XML, automatic population of server-side JavaBeans, Web forms with validation, and some internationalization support. It also includes a set of custom tags for accessing server-side state, creating HTML, performing presentation logic, and templating. Some vendors have begun to adopt and evangelize Struts. Struts has a great deal of mindshare, and can be considered an industrial-strength framework suitable for large applications.

In Struts, requests are routed through a controller servlet. Action objects control request handling, and these actions use components such as JavaBeans to perform business logic. Struts elegantly creates a full dispatch mechanism on top of servlets with an external configuration, eliminating the artificial tie between URLs and online activities. Nearly all requests come in through the same servlet, client requests indicate as part of the request the action they'd like to take (i.e., login, add to cart, checkout), and the Struts controller dispatches the request to an Action for processing. JSP is used as the presentation layer, although it also works with Apache Velocity and other technologies. Struts is an open source project and was developed under the Apache model of open, collaborative development.

JavaServer Faces
JavaServer Faces (JSF) is a Sun-led Java Community Process effort (JSR-127) still in the early development stage. It's just reaching the first stage of Community Review as of this writing, but already it's gaining terrific mindshare. The JSF proposal document contains plans to define a standard web application framework, but the delivery appears to focus on the more limited goal of defining a request-processing lifecycle for requests that include a number of phases (i.e., a form wizard). It's a JSF goal to integrate well with Struts.

Apache Turbine
Apache Turbine might be one of the oldest servlet frameworks, having been around since 1999. It has services to handle parameter parsing and validation, connection pooling, job scheduling, caching, database abstractions, and even XML-RPC. Many of its components can be used on their own, such as the Torque tool for database abstraction. Turbine bundles them together, providing a solid platform for building web applications the same way J2EE works for enterprise applications.

Turbine, like the other frameworks, is based on the MVC model and action event abstraction. However, unlike the rest, Turbine provides extra support at the View layer and has dubbed itself "Model 2+1" as a play on being better than the standard "Model 2" MVC. Turbine Views support many template engines, although Apache Velocity is preferred.

We could discuss many more frameworks if only we had the space. If you're interested in learning more, Google away on these keywords: TeaServlet, Apache Cocoon, Enhydra Barracuda, JCorporate Expresso, and Japple.

Use Pre-Encoded Characters

One of the first things you learn when programming servlets is to use a PrintWriter for writing characters and an OutputStream for writing bytes. And while that's stylistically good advice, it's also a bit simplistic. Here's the full truth: just because you're outputting characters doesn't mean you should always use a PrintWriter!

A PrintWriter has a downside: specifically, it has to encode every character from a char to a byte sequence internally. When you have content that's already encoded--such as content in a file, URL, or database, or even in a String held in memory--it's often better to stick with streams. That way you can enable a straight byte-to-byte transfer. Except for those rare times when there's a charset mismatch between the stored encoding and the required encoding, there's no need to first decode the content into a String and then encode it again to bytes on the way to the client. Use the pre-encoded characters and you can save a lot of overhead.

To demonstrate, the servlet in Example 3-1 uses a reader to read from a text file and a writer to output text to the client. Although this follows the mantra of using Reader/Writer classes for text, it involves a wasteful, needless conversion.

Example 3-1: Chars in, chars out

import java.io.*;
import java.util.prefs.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class WastedConversions extends HttpServlet {

  // Random file, for demo purposes only
  String name = "content.txt";

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                              throws ServletException, IOException {

    String file = getServletContext(  ).getRealPath(name);

    res.setContentType("text/plain");
    PrintWriter out = res.getWriter(  );

    returnFile(file, out);
  }

  public static void returnFile(String filename, Writer out)
                             throws FileNotFoundException, IOException {
    Reader in = null;
    try {
      in = new BufferedReader(new FileReader(filename));
      char[  ] buf = new char[4 * 1024];  // 4K char buffer
      int charsRead;
      while ((charsRead = in.read(buf)) != -1) {
        out.write(buf, 0, charsRead);
      }
    }
    finally {
      if (in != null) in.close(  );
    }
  }
}

The servlet in Example 3-2 is more appropriate for returning a text file. This servlet recognizes that file content starts as bytes and can be sent directly as bytes, as long as the encoding matches what's expected by the client.

Example 3-2: Bytes in, bytes out

import java.io.*;
import java.util.prefs.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class NoConversions extends HttpServlet {

  String name = "content.txt";  // Demo file to send

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    String file = getServletContext(  ).getRealPath(name);

    res.setContentType("text/plain");
    OutputStream out = res.getOutputStream(  );

    returnFile(file, out);
  }

  public static void returnFile(String filename, OutputStream out)
                             throws FileNotFoundException, IOException {
    InputStream in = null;
    try {
      in = new BufferedInputStream(new FileInputStream(filename));
      byte[  ] buf = new byte[4 * 1024];  // 4K buffer
      int bytesRead;
      while ((bytesRead = in.read(buf)) != -1) {
        out.write(buf, 0, bytesRead);
      }
    }
    finally {
      if (in != null) in.close(  );
    }
  }
}

How much performance improvement you get by using pre-encoded characters depends on the server. Testing these two servlets against a 2 MB file accessed locally shows a 20% improvement under Tomcat 3.x. Tomcat 4.x shows a whopping 50% improvement. Although those numbers sound impressive, they of course assume that the application does nothing except transfer text files. Real-world numbers depend on the servlet's business logic. This technique (illustrated in Figure 3-2) are most helpful for applications that are bandwidth- or server CPU-bound.

Figure 3-2. Taking advantage of pre-encoded characters

Figure 3-2. Taking advantage of pre-encoded characters

The principle "Use Pre-encoded Characters" applies whenever a large majority of your source content is pre-encoded, such as with content from files, URLs, and even databases. For example, using the ResultSet getAsciiStream( ) method instead of getCharacterStream( ) can avoid conversion overhead for ASCII strings--both when reading from the database and writing to the client. There's also the potential for cutting the bandwidth in half between the server and database because ASCII streams can be half the size of UCS-2 streams. How much benefit you actually see depends, of course, on the database and how it internally stores and transfers data.

In fact, some servlet developers preencode their static String contents with String.getBytes( ) so that they're encoded only once. Whether the performance gain justifies going to that extreme is a matter of taste. I advise it only when performance is a demonstrated problem without a simpler solution.

To mix bytes and characters on output is actually easier than it probably should be. Example 3-3 demonstrates how to mix output types using the ServletOutputStream and its combination write(byte[ ]) and println(String) methods.

Example 3-3: ValueObjectProxy.java

import java.io.*;
import java.sql.*;
import java.util.Date;
import javax.servlet.*;
import javax.servlet.http.*;

public class AsciiResult extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/html");
    ServletOutputStream out = res.getOutputStream(  );

    // ServletOutputStream has println(  ) methods for writing strings.
    // The println(  ) call works only for single-byte character encodings.
    // If you need multibyte, make sure to set the charset in the Content-Type
    // and use, for example, out.write(str.getBytes("Shift_JIS")) for Japanese. 
    out.println("Content current as of");
    out.println(new Date(  ).toString(  ));

    // Retrieve a database ResultSet here.

    try {
      InputStream ascii = resultSet.getAsciiStream(1);
      returnStream(ascii, out);
    }
    catch (SQLException e) {
      throw new ServletException(e);
    }
  }

  public static void returnStream(InputStream in, OutputStream out)
                             throws FileNotFoundException, IOException {
    byte[  ] buf = new byte[4 * 1024];  // 4K buffer
    int bytesRead;
    while ((bytesRead = in.read(buf)) != -1) {
      out.write(buf, 0, bytesRead);
    }
  }
}

Although mixing bytes with characters can provide a performance boost because the bytes are transferred directly, I recommend you use this technique sparingly because it can be confusing to readers and can be error-prone if you're not entirely familiar with how charsets work. If your character needs to extend beyond ASCII, be sure you know what you're doing. Writing non-ASCII characters to an output stream should not be attempted by a novice.

Pages: 1, 2, 3, 4

Next Pagearrow