HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

The Java servlet API specifies a very lightweight framework for developing Web applications. Although servlet technology is just one of the building blocks in the J2EE architecture, developers often use servlets to build full-fledged Web applications. Today several vendors and organizations provide servers and containers that implement the servlet API. For an overview of the servlet programming model, and some of the advanced features of Java servlets, refer to Part 1 of this article (JDJ, Vol. 5, issue 2).

As a lightweight framework, the servlet API doesn't impose a very strict programming model. Specifically, the API specification leaves certain design decisions to the developers. The servlet specification also added and withdrew (deprecated) certain features over its evolution. Unfortunately, though such deprecation was necessary to bring robustness to the servlet model, such changes sometimes cultivated incorrect programming practices. In addition, container vendors follow different models for implementing the API specification. In such a scenario it's essential for servlet programmers to be careful while making assumptions regarding those aspects that aren't covered by the servlet specification.

In this article I'd like to conduct an analysis of some of the design practices that servlet developers often undertake while developing Web applications. I draw some of the inputs for this article from the archives of the Servlet Interest Mailing List. Note: Some of the practices may not affect small-scale Web applications. The focus of my article is on servlets for large-scale Web applications that are required to be portable and scalable. While discussing these practices, I assume the containers will provide advanced features such as clustering, failover, and more.

Explicit Servlet Instantiation
This is a novice practice. Experienced programmers would probably snub me for mentioning this because they never do it. Nonetheless, allow me to elaborate on why you shouldn't instantiate servlets explicitly.

In brief, the hosting servlet container creates servlet instances. Instances created explicitly by applications can't receive HTTP requests from clients.

A servlet container creates servlet instances in response to HTTP requests. As discussed in Part 1 of this article, the purpose of a container is to receive HTTP requests, construct or locate a servlet instance, construct the environment for the servlet instance (a ServletContext), construct the request and response objects (the HttpServletRequest and HttpServletResponse objects), and delegate the incoming request to the servlet instance.

The key point here is that it's the servlet container's responsibility (and prerogative) to create servlet instances and delegate requests. Both the threading model that the developer follows and the instantiation model implemented by servlet containers dictate how and when servlet instances are created.

If a servlet implements the SingleThreadModel interface, the servlet container delegates each concurrent request to a different servlet instance or serializes incoming requests so that a single instance handles all the incoming requests, one after the other. If your LoginServlet implements the SingleThreadModel interface and there are five concurrent requests to this servlet, there are two possibilities. The first is that the container will delegate these five requests to five different instances of the LoginServlet. Alternatively, each of these login requests will be handled in series by just one instance. Once all the requests are served, the container may hold the instance(s) in a pool and reuse them for future requests.

For servlets that don't implement the SingleThreadModel interface, the instantiation policy depends on the container implementation. While some containers maintain a pool of instances, with each instance handling requests in different threads, some other containers delegate all requests in different threads to just one instance. However, the new Java Servlet API 2.2 specification mandates that a container must maintain one instance per servlet per Java Virtual Machine in an application.

Servlet instances are created and destroyed according to the policies . There's nothing that actually prevents you from creating a servlet instance as long as the LoginServlet class is available in the container's class path. Such an instance will be independent of the host servlet container. The container can't know about it. It can't manage the life cycle of such an instance. You're probably loading the servlet container in some unwanted way.

What's Wrong with Instance Variables?
What's wrong with instance variables in servlets? After all, servlets are normal Java objects. Well, yes, they are normal Java objects. But the servlet container manages them and that makes all the difference.

There are at least two reasons why a servlet developer would want to have instance variables in a servlet. One is to store request-specific state (e.g., state pertaining to a user session including any conversational state); the second is to store state that's common to several requests (e.g., application state).

In either case servlet instance variables are not the best solution for handling state. Let me rule it out completely before we discuss better solutions provided by the specification.

In addition to the container's instantiation model and the servlet's threading model discussed above, two more issues are to be considered here:

  1. Clustering and load balancing: As discussed in Part 1, if your container provides clustering facilities, you can mark your Web applications as distributable. In this case the container instantiates the servlets in multiple JVMs. Although the servlet specification requires a sticky load-balancing strategy, container vendors may choose to implement instance-independent load balancing with distributed sessions and state. In such cases there's no guarantee that all requests from the same client session will be handled by the same JVM, and hence the same set of instances.
  2. Swapping user sessions: Containers are free to swap user sessions and the associated load from one node to another in a cluster. In case of failure - say, a crash - of one JVM on one host, the cluster can also be configured to shift the load (and the session data) to another JVM (failover) or to restart the failed node.
Thus there's no guarantee that the same servlet instance will receive all the requests (from one user or all users) in a Web application.

Therefore, if not by specification, servlets are stateless by implication.
What's the solution for handling state?

The best solution for handling request-specific state is to store it in the HttpSession object associated with the request. In the case of distributable servlet applications, the Servlet API 2.2 specification mandates that such state be serializable. It's good practice to ensure that all your session variables are serializable whether your application is distributable or not. Who knows? When your user base increases, you may want your Web application to be distributable.

For handling application-specific state (say, a list of addresses that's common to all users), servlet containers provide you with a ServletContext object. This object is similar to HttpSession in functionality. The main difference is that while an HttpSession object is specific to a client session, a ServletContext object is specific to a Web application on a given container. Irrespective of user sessions, all servlets in a Web application in a given container can access and share information via a ServletContext object.

What happens if your application is distributable and you want your application state stored in the ServletContext object to be shared across containers in a cluster? The servlet API doesn't provide for this.

The specification doesn't guarantee any kind of persistence of the state information (whether you store it in HttpSession or in ServletContext objects). In case you require persistent state management, it's better to devise your own mechanism, using some external data storage (files or databases), or delegate this to some other back-end component or system. Alternatively, check with your container vendor.

What about using static variables in servlets? There are three points to consider:

  1. Are your static variables read-only? If not, you need to synchronize while updating such variables.
  2. In case your static variables aren't read-only, you should also consider the effects of the distribution of the application. How do you make sure that static variables in different JVMs are consistent?
  3. How do you protect your static variables/methods from other Web applications deployed on the same container (and JVM)?
To avoid these issues, consider storing such data in ServletContext objects.

SingleThreadModel - Why? And Why Not?
When and why should you implement the SingleThreadModel interface? This is a dilemma often faced by servlet developers.

Let me address the why part first.

Your servlets may implement the SingleThreadModel interface to make them (but not necessarily the resources that your servlets access) thread-safe. To quote from the API documentation: "If a servlet implements this interface, you are guaranteed that no two threads will execute concurrently in the servlet's service method. The servlet container can make this guarantee by synchronizing access to a single instance of the servlet, or by maintaining a pool of servlet instances and dispatching each new request to a free servlet."

Thus the specification guarantees that an instance of a SingleThreadModel servlet handles one incoming HTTP request at a time. Your servlets should implement this interface if you want to protect instance variables and other nonshareable resources from multiple request threads. But this doesn't guarantee that requests to a SingleThreadModel servlet will be handled sequentially. As discussed above, this is implementation-specific. Don't expect a "hit counter" servlet to work with a SingleThreadModel. This doesn't suffice.

The same effect can be achieved by synchronizing the various entry points (methods such as init(), service(), destroy(), etc.) into a servlet instance from the container. In this case the specification requires that containers serialize requests to that servlet instance instead of creating an instance pool, as is done in the case of SingleThreadModel servlets. However, you may not be required to take such an extreme step. For performance reasons it's better to narrow down the synchronization blocks in your code.

To summarize: three threading scenarios are possible (see Table 1).

Table 1

What About Connection Pools?
How do you manage connections to databases and other resources/systems in a servlet-based Web application?

First, why do you need connection pooling? It saves the connection creation and disposal overheads. Once your application gets a connection to a relational database and finishes processing the database updates, it's better if it retains the connection object for future use. For efficient resource utilization you need connection pooling, which is a mechanism for recycling your connection objects.

Let's now examine some typical connection pooling strategies that servlet developers adopt. I draw these strategies from Java Servlet Programming and from various discussions from the Archives of the Servlet Interest Mailing List.

Connection Object as an Instance Variable
In this approach each servlet maintains its own connection object as an instance variable. The connection object is usually created in the init() method of the servlet. Thus each instance of the servlet holds a connection object. Look at the sample code in Listing 1. This approach is adequate for nontransactional database updates in the autocommit mode, since connection objects are specified to be thread-safe. But what happens if you want to implement multiple updates/inserts within a single transaction?

In the case of transactions your database server will associate each connection object with a single transaction. Consider the case of a servlet implementing a transaction. If an instance of this servlet is processing two concurrent requests in different threads, your transactions get mixed up. Instead of two transactions (as you'd expect), the database sees only one. Try the Java program in Listing 2. The TestThread class emulates a servlet instance processing two concurrent requests. In this class two threads are trying to do different transactions in two threads on the same connection object. You'll find that only those updates that occur after the rollback in the second thread will be committed to the database. Try it with different sleep intervals. The result? This approach doesn't preserve atomicity of transactions.

You can avoid this by synchronizing the transactional calls on the connection object as a group (try synchronizing the transact() method in Listing 2) or by implementing the SingleThreadModel interface, but your servlets will be penalized in terms of performance.

This solution isn't safe for transactional database access.

Connection Object Held in HttpSession
Instead of holding the connection object as an instance variable, you may want to store it in the HttpSession associated with a client so that all servlets serving a client may reuse the connection object. However, you should consider the following here:

  • What happens if your servlet container chooses to serialize some of the session objects to conserve memory or to shift the load from one JVM to another in the same cluster? The serialization process would fail since connection objects aren't serializable.
  • In case your application is distributable, the container requires that the session variables be serializable. If you try to pass a connection object to the HttpSession, the container may throw an IllegalArgumentException.
  • What happens if your application is serving a thousand concurrent users? Your application would attempt to get a thousand connection objects from your database server.
As you can see, the foregoing solution isn't appropriate for developing production quality applications.

Connection Pool Managers
Another widely used solution for connection pooling is to develop a connection pool manager. Such a manager can take at least three forms: a singleton, a static class or a servlet with static attributes/methods. In all these cases the manager class would provide accessor methods to manage a pool of connection objects.

This solution may open up a major security breach because such a manager class/object is accessible from all servlets within the same JVM. If your servlet container is hosting multiple applications, there's nothing to prevent a rogue servlet from getting hold of one of the connection objects and destroying your database tables. Be wary of such a solution. You may need to provide an additional security mechanism (using the protection domains and principles of Java 2 security architecture) for returning connection objects only to trusted objects.

The DBConnectionBroker toolkit from Java Exchange recommends an alternative approach to the use of connection pool manager. This toolkit provides a DBConnectionBroker class to implement a connection manager and an HttpServletJXGB servlet that holds it as a static protected variable. This servlet is supposed to serve as a base class for all your servlets requiring database access. Instances of the derived servlet classes can therefore make use of the connection pool manager. In servlet containers before version 2.2, if servlets from multiple applications deployed on the same JVM extend from the same HttpServletJXGB class, there's still scope for the security issue mentioned above.

JDBC 2.0 Optional Package Extension API
This Optional Package Extension API has several enhancements over the standard JDBC API. One of the enhancements is connection pooling. In this API the getConnection() method of the javax.sql.DataSource class returns connection objects. Depending on how the DataSource class is implemented, the getConnection method can return pooled connections. A significant implication of this approach is that the responsibility of connection pooling is delegated to the driver implementer. In addition to the above API, some of the JDBC driver vendors also implement connection pooling. Check the list of vendors at http://java.sun.com/products/jdbc/drivers.html.

Of all the approaches discussed in this section, database drivers complying with the JDBC 2.0 Optional Package Extension API offer the most robust solution for connection pooling.

Servlets and Operating Environment Resources
Application Threads

Can a servlet start new threads? Not in all cases, as discussed below. The environment in which servlet instances operate is guaranteed to be valid only during the course of a client request, that is, during the service() method. This environment includes the HttpSession, ServletContext, HttpServletRequest and HttpServletResponse objects. Make sure your application threads don't refer to these objects beyond the context of the service() method. In case you need specific information from these objects, consider copying such data into temporary objects (defined to suit your requirements) before starting any thread, and program the threads to use data from these temporary objects.

Security
As indicated in the Servlet Specification 2.2, a servlet container may impose additional security restrictions on the accessing resources in the servlet environment. These resources include the JVM (threads, etc.), network, file system, and more. Although none of the container vendors seem to be moving in this direction currently, we can expect such restrictions from specialized servlet containers or domain-specific e-commerce products.

File System
The servlet specification doesn't guarantee any specific "current working directory" from which you can access text files, configuration files, images, and so on. Two approaches can guarantee container-independent file system access. The first approach is to keep all such resources accessible to the class path, and use the getResource() or getResourceAsStream() methods of the class loader to get the URL or an InputStream corresponding to resources. Alternatively, you can store the absolute locations of such resources on the file system as initialization (init-param) or context (context-param) entries in the associated deployment descriptor.

Summary
As Douglas Bennet points out, software is soft because of the soft programming constructs used to build software. There are many ways to implement a piece of functionality and many ways to use an API. Although most of these ways might work, not all of them may be resilient to changes. It's not always easy to grasp the implications of our designs, and there's always scope for debate. Nonetheless, there are better means to be considered that lead to better applications.

In this article I discussed some of the design practices with Java servlets. While most of the discussion is based on the Java Servlet Specification v2.2, by no means is it complete. The particular practices chosen for this discussion were motivated more by the need to probe into the intricacies involved in servlet development than on the practices per se.

References

  1. Java Servlet Specification v2.2:
    http://java.sun.com/products/servlet/2.2/index.html
  2. Archives of Servlet-Interest Mailing List:
    http://archives.java.sun.com/archives/servlet-interest.html
  3. Hunter, J., and Crawford, W. (1998). Java Servlet Programming. O'Reilly & Associates.
  4. Gamma, E., Helm,R., Johnson, R., and Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley.
  5. Db Connection Broker:
    www.JavaExchange.com
  6. The JDBC 2.0 Optional Package:
    http://java.sun.com/j2ee/bulletin/jdbc_2/extension.html
  7. Bennet, D.W., (1996). Hard Software: The Essential Tasks. Manning Publications.
Author Bio
Dr. Subrahmanyan is a technical consultant with the electronic commerce division of Wipro Technologies, based in Bangalore, India. You can visit him at his Web site, www.Subrahmanyam.com.
Dr. Subrahmanyan can be contacted at : [email protected].

	

Listing 1: Connection Object as an Instance Variable
    
public MyServlet extends HttpServlet {
  private Connection connection;
  public init(ServletConfig config) {
    // Get database details from config
    ...
    // Create a connection object
    connection = ...
  }

  public void service(HttpServletRequest req, HttpServletResponse res) {
    // Use the connection object for database access
    ...
  }
}



Listing 2: Connection Object- One Transaction or Two Transactions?

import java.sql.*;
class Transaction {
  private Connection conn;
  Transaction() {
    String dburl = "jdbc:odbc:Test";
    String user = "scott";
    String password = "tiger";
    try {
       Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
       conn = DriverManager.getConnection(dburl, user, password);
       System.out.println("Connection created.");
    } catch(Exception e) { e.printStackTrace(); }
  }
  void transact(int action) {
    Statement stmt = conn.createStatement();
    try {
      conn.setAutoCommit(false);
      switch (action) {
      case 1 :
        stmt.executeUpdate("insert into dept val-
        ues(41,'100','100')");
        System.out.println("1");
        try { Thread.sleep(5); catch(Exception e) {}
        stmt.executeUpdate("insert into dept val-
        ues(51,'101','101')");
        System.out.println("11");
        stmt.executeUpdate("insert into dept val-
        ues(62,'102','102')");
        System.out.println("111");
        try { Thread.sleep(2); } catch(Exception e) {}
        conn.commit();
        System.out.println("1 commited");
        break;
      case 2 :
        stmt = conn.createStatement();
        stmt.executeUpdate("update dept set dname='SomeAc-
        counting' where deptno=10");
        System.out.println("2");
        stmt.executeUpdate("update dept set dname='SomeAc-
        counting' where deptno=20");
        System.out.println("22");
        try { Thread.sleep(5); } catch(Exception e) {}
        conn.rollback();
        System.out.println("2 rolled back");
        stmt.executeUpdate("update dept set loc='Bangalore'  
        where deptno=10");
        System.out.println("222");
        break;
      default :
        break;
      }
    } catch(Exception e) { e.printStackTrace(); }
  }
}

public class TestThread extends Thread {
  Transaction t;
  int action;
  public TestThread(Transaction tThread, int action) {
    t = tThread;
    this.action = action;
  }
  public void run() {
    t.transact(action);
  }
  public static void main(String s[]) {
    Transaction t = new Transaction();
    TestThread t1 = new TestThread(t, 1);
    TestThread t2 = new TestThread(t, 2);
    t1.start(); 
    t2.start();
  }
}





 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.