The Role of Servlets in Web Application Design in java

www.spiroprojects.com



The Java servlet architecture provides an excellent framework for server-side processing. Because servlets are written in Java, they can take advantage of Java's memory management and rich set of APIs. Servlets can also run on numerous platforms and HTTP servers without change. Further, the servlet architecture solves the major problem of the CGI architecture: a servlet request spawns a lightweight Java thread, while a CGI request will spawn a complete process. Unless the project has a large budget for high-end Java-based application server projects, servlets offer the best technology to develop server-side components and Web-based applications.
This article, however, is not about the many advantages of the servlet architecture (see Resources   for articles covering that); rather, it is about how to use servlets to generate responses efficiently, use less memory in the process, and stream the generated responses back to the client, making response times seem even better. Applying the techniques in this article will allow you to build servlets that can serve more requests and serve them up faster. Based on that foundation, you can build enterprise Web applications that will scale to your needs, run 24x7, and provide the best response times. To aid my discussion, I use a simple example servlet that counts down from 10 to 1. Each example of the countdown servlet is slightly modified to demonstrate a specific technique. By focusing on the techniques, rather than a fancy example, you'll see how to apply the specific techniques to solve problems in your own servlets.
Note: This article assumes you're familiar with the servlet architecture and have experience writing servlets. See Resources  for tutorial information.

Effective servlets

There are two major goals to building servlets for efficient HTML generation. The first goal, especially important for high-volume Web applications, is to carefully control the amount of garbage created during page generation. The more garbage created, the fewer pages generated, and the slower the response time for a request. This bottleneck occurs because the Java VM has to steal cycles to run garbage collection when it should be generating pages. If too much garbage is created, request processing could halt entirely. This is unacceptable for critical Web applications. These problems can be largely avoided using simple ecological techniques: reduce, reuse,and recycle. We'll discuss these techniques in more detail in just a moment.
The second goal of efficient HTML generation is to keep the browser active: the typical Web surfer doesn't have much tolerance for blank gray pages and "waiting for response" messages. The time a Web application spends querying a database or accessing a legacy system is typically the time a user spends staring at a blank page. This can -- and should -- be avoided using servlet response streaming techniques. As we'll see, these techniques dovetail well with the aforementioned ecological techniques, and together they form a foundation for building solid, well-behaved servlets.
Servlet response streaming techniques are also useful for much more than just generating HTML. For servlets, they can be applied when generating XML, JavaScript, GIF or JPEG images, or any other protocol servlets support. In addition, the garbage management techniques we'll cover can be applied to almost any area in Java, especially to applications that must run for an extended period of time.
So let's get started.

Managing garbage

Garbage collection is one of the most attractive features of Java, and servlets can take full advantage of it. Garbage collection still requires a certain amount of overhead, which can be a problem -- especially for server-side applications that generate Web pages dynamically. Server-side applications typically run continuously for extended periods of time. Memory problems that might go unnoticed in client-side applications (like applets or Java GUI applications) could greatly effect the performance of a server-side application in just a few hours of continuous, high-volume operation.
Web page generation requires special consideration, as it uses extensive string manipulation and concatenation. Remember, all Strings in Java are immutable, so it's easy to unintentionally create multiple String objects instead of just one. Creating many unnecessary objects can be considered "ecologically unsound" because additional effort -- in the form of CPU cycles -- is required to clean up the garbage. The more garbage created, the more effort required.
By applying our three simple ecological principles -- reduce the number of objects created, reuse the objects that are created, and recycle objects that are no longer needed -- we can develop servlets that are capable of operating for longer periods of time with less overhead. This process results in Web applications that are more robust and better behaved.
Let's take a look at what each of these principles means in practice.
Reduce
One of the most effective ways to avoid the overhead of garbage collection is simply to reduce the number of objects being created. At first this may sound naive, but thinking carefully about how the objects are being used can lead to simplified and more efficient code. Take the following example:
Example 1
public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    String html = "<html><head><title>Example 1</title></head>\n";
    html += "<body>\n";
    html += "<h1>Countdown</h1>\n";
    for ( int i = 10; i > 0; i-- ) {
        html += "<h1>" + i + "</h1>";
    }
    html += "<h1>Liftoff</h1>\n";
    html += "</body></html>\n";
    PrintWriter out = res.getWriter();
    out.println( html );
    out.close();
}


Note: This example, while slightly contrived to demonstrate the point, represents what a servlet might look like if ported directly from a Perl CGI script.
The above implementation will create 14 Strings and 13 StringBuffers. Most of these objects are created inside the for loop; each time through the loop a String and a StringBuffer pair are created. A much cleaner way to implement the same method would be to simply write the output directly to the ServletResponse's PrintWriter:
Example 2
public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    PrintWriter out = res.getWriter();
    out.println( "<html><head><title>Example 2</title></head>" );
    out.println( "<body>" );
    out.println( "<h1>Countdown</h1>" );
    for ( int i = 10; i > 0; i-- ) {
        out.print( "<h1>" );
        out.print( i );
        out.println( "</h1>" );
    }
    out.println( "<h1>Liftoff</h1>" );
    out.println( "</body></html>" );
    out.close();
}

This example generates the same HTML output without creating any objects at all. The difference here is that all output is written directly to the PrintWriter, which does not require creating any intermediate objects. Because fewer objects are created and less memory is used, garbage collection is needed much less frequently. This means more pages can be generated -- and more requests served -- with the same resources.
You can download all the examples in this article from Resources. Each is contained in fully functional servlets, named Example1.java, Example2.java, etc. The actual source code contains comments about the servlet functions, but the examples are intended for use with this article.
Reuse
It isn't always reasonable, or even possible, to simply write all output directly to the PrintWriter provided in the ServletResponse. For example, in situations where a portion of the HTML is going to be generated before the response is served, it makes sense to use some sort of buffering mechanism. As I demonstrated in our first example, the String object can't be used as a buffer. Behind the scenes, the Java compiler really creates a StringBuffer and a String for each line of code that does any string manipulation. This doesn't help reduce the number of objects being created.
A StringBuffer is the best way to manipulate string values. You can create a StringBuffer, use it to concatenate several Strings or other values (including primitives), and then convert it into a single String. Further, the String and StringBuffer will work together, sharing the same memory until the StringBuffer is modified. The following example shows how you can use a StringBuffer instead of String concatenation:
Example 3
public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    StringBuffer buffer = new StringBuffer( 1024 );
    buffer.append( "<html><head><title>Example 3</title></head>" );
    buffer.append( "<body>" );
    buffer.append( "<h1>Countdown</h1>" );
    for ( int i = 10; i > 0; i-- ) {
        buffer.append( "<h1>" );
        buffer.append( i );
        buffer.append( "</h1>" );
    }
     buffer.append( "<h1>Liftoff</h1>" );       
    buffer.append( "</body></html>" );
    PrintWriter out = res.getWriter();
    out.println( buffer.toString() );
    out.close();
}
In this example, the StringBuffer isn't altered after the call to toString(), so the char array backing the StringBuffer is never copied, which reduces the memory use. The countdown loop in this example demonstrates yet another memory reduction technique. It could have been written
buffer.append( "<h1>" + i + "</h1>" );
but this would have resulted in a String and StringBuffer being created for each pass through the loop.
Recycle
One final note about managing garbage: the garbage collector can't collect garbage it thinks is still being used. Make sure at the end of your page-generation process that all unneeded objects are no longer referenced. If the servlet is completely stateless -- that is, it doesn't need any data to persist between requests -- all objects should be eligible for garbage collection. Keeping references to unneeded objects is similar to a memory leak; memory will grow with each request serviced, but the garbage collector won't be able to reclaim the memory.
Even though Java's garbage collection mechanism greatly reduces the hassle of memory management, there are still some tricks to making garbage collection a little more effective. Create fewer objects, reuse objects, and make sure the garbage collector knows when objects are no longer used. This will help your servlets lead longer and happier lives.

Streaming output

While efficient servlets are important, efficiency isn't our sole concern. Servlets, assuming they aren't trivial examples like the ones we've covered so far, actually have to perform an operation; querying a database using JDBC or accessing an external system using CORBA. The most efficient servlet ever written can't speed up a complex DB2 query on an old mainframe. The end user doesn't care about effective memory use; the end user wants results. And waiting for a query to complete before responding to a request won't get the job done. Obviously, some bottlenecks on the back end can't be avoided, but the user doesn't have to know about it. By streaming output directly to the browser, your end users will be blissfully unaware of the back end bottleneck.
Before jumping into the solution, let's take a closer look at the problem. Here is another look at our countdown servlet, this time with a sleep() added to simulate accessing an external system:
Example 4
public void doGet ( HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
{
    res.setContentType( "text/html" );
    PrintWriter out = res.getWriter();
    out.println( "<html><head><title>Example 4</title></head>" );
    out.println( "<body>" );
    out.println( "<h1>Countdown</h1>" );
    for ( int i = 10; i > 0; i-- ) {
        out.print( "<h1>" );
        out.print( i );
        out.println( "</h1>" );
        try {
            // simulate access to an external system
            Thread.sleep( 1000 );
        }
        catch ( InterruptedException e ) {}
    }
    out.println( "<h1>Liftoff</h1>" );       
    out.println( "</body></html>" );
    out.close();
}

In this example, I've added a 1-second sleep() to the countdown loop. While this works nicely for the specific example, the real purpose of the pause to is to demonstrate the effects of the lag time created by accessing a database or legacy system. Here the effect isn't quite as expected: instead of seeing the numbers counting down with a 1-second interval between each, we get one 10-second pause and then the whole page is displayed at once. This happens because the PrintWriter is buffered and nothing is sent back to the browser until the output stream is close()d at the end of the method.

Previous
Next Post »