Lifecycle Events Monitor Context Changes

Application- and session-level events introduced in the Servlet 2.3 API can help make your servlet and JSP apps even better
by Budi Kurniawan

The Servlet 2.3 API specification, now at its public final draft stage, offers new features to servlet and JavaServer Pages (JSP) developers—most noticeably, new application and session events that can help you write more effective and efficient servlet and JSP applications. Even though Servlet 2.3 is not yet released, the beta version of Tomcat 4.0 is compatible with the new application and session events, so you don't have to wait to reap the benefits.

Most servlet and JSP developers have probably been using the javax.servlet.ServletContext and javax.servlet.http.HttpSession interfaces. A ServletContext object is created by the servlet/JSP container and can be used to share information among resources in the same application. In particular, ServletContext offers two methods, getAttribute() and setAttribute(), that make the interface behave like a hashtable. With these two methods, you can store and retrieve objects in a key/value pair format. HttpSession is used to store and retrieve information in the scope of a user session. Like ServletContext, you store key/value pairs in an HttpSession object. However, unlike ServletContext, which is accessible to all the resources, an HttpSession object is associated with a user and the object's key/value pairs are available only for that user.

The Servlet 2.3 API specification provides application- and session-level events that you can use with the ServletContext and HttpSession objects. The concept is simple.

Application Events
In relation to the ServletContext object, the Servlet 2.3 API enables you to receive notifications when the ServletContext object is created and destroyed, or when an attribute (a key/object pair) is created, removed, or replaced. The moments when these things happen become Java events that you can listen to.

What is the use of an application-level event? It can be very useful because you can create a method that gets called automatically when an event occurs. As an example, you may want to do some initialization when the servlet/JSP container starts up, such as load a class library or open a file. Because at startup the servlet/JSP container creates the ServletContext object and the ServletContext interface triggers an event when an instance of it is created, you can write a method that is hooked on to this event. As a result, when the container starts up, your code will execute automatically.

The creation, destruction, or modification of an attribute in the ServletContext object also triggers events. Likewise, the destruction of ServletContext triggers an event. Responding to this event, you can write some clean-up code that frees the resources used in the application.

You can use two listener interfaces in the javax.servlet package to capture these application-level events. The first interface is ServletContextListener, which can provide you with notifications about the creation or destruction of the ServletContext object. The second interface is ServletContextAttributesListener, which can capture events related to attribute creation, deletion, and modification in the ServletContext object.

The ServletContextListener interface has this signature:

public interface 
   ServletContextListener 
   extends java.util.EventListener

This interface has two methods, contextInitialized() and contextDestroyed():

public void contextInitialized(
   ServletContextEvent sce)
public void contextDestroyed(
   ServletContextEvent sce)

The first method, contextInitialized(), is triggered when the Web container creates the ServletContext object. In Tomcat, it is called after Tomcat finishes its own initialization process. The contextDestroyed() method is called when the servlet context is about to be shut down. If you want, you can override this method and write code that does some cleaning up, such as closing files or database connection.

Both the contextInitialized() and the contextDestroyed() methods pass a ServletContextEvent class:

public class ServletContextEvent 
   extends java.util.EventObject

The only method in ServletContextEvent, getServletContext(), returns the ServletContext that has changed.

Now that you know how to write a listener class, you need to tell the servlet/JSP container about your class, so that the container can invoke methods in your class. The way to inform the container is by using a listener element in the deployment descriptor of the Web application. The listener element appears as a subelement of the web-app element. It has one subelement: listener-class. The value of the listener-class element is the fully qualified name of the listener class. Here is an example of a deployment descriptor that contains a listener class:

<web-app>
   <listener>
      <listener-class>
         com.ecorpnu.web.
            MyAppEventListener
      </listener-class>
   </listener>
</web-app>

Note that the DTD file for the Servlet 2.3 deployment descriptor states this element descriptor for the web-app element:

<!ELEMENT web-app (icon?, display-
   name?, description?, 
   distributable?, context-param*, 
   filter*, filter-mapping*,
   listener*, servlet*, servlet-
   mapping*, session-config?, mime-
   mapping*, welcome-file-list?, 
   error-page*, taglib*, resource-
   env-ref*, resource-ref*, security-
   constraint*, login-config?,
   security-role*, env-entry*, ejb-
   ref*, ejb-local-ref*)>

In this descriptor, a question mark indicates that an element is optional and can appear only once, and an asterisk is used to specify an element that can appear zero or more times in the deployment descriptor. The description of the web-app element indicates that all subelements are optional. Subelements must appear in the order specified. For example, if the web-app element in a deployment descriptor has both listener and servlet subelements, the listener subelement must appear before the servlet subelement. Also, the element descriptor for web-app allows you to use more than one listener element, if you wish.

Listing 1 shows a listener class that listens to the lifecycle events of the ServletContext. It simply prints the string "Application initialized" when the ServletContext is initialized and "Application shut down" when the ServletContext is destroyed. For the MyApplicationListener class to work, you must register it in the deployment descriptor (see Listing 2).

To test the example, you need to restart Tomcat. Once you start it, you should be able to see "Application initialized" on the console window.

In addition to a lifecycle listener, you can also implement the ServletContextAttributeListener class to receive notification when an attribute is added to the ServletContext, or if any of the ServletContext's attributes are changed or removed. This interface has three methods that you need to override. The three methods are attributeAdded(), attributeRemoved(), and attributeReplaced(). The signatures for these methods are as follows:

public void attributeAdded(
   ServletContextAttributeEvent 
   scae)
public void attributeRemoved(
   ServletContextAttributeEvent 
   scae)
public void attributeReplaced(
   ServletContextAttributeEvent 
   scae)

The first method, attributeAdded(), is called when a new attribute is added to the ServletContext object. The attributeRemove() method is called when an attribute is deleted from the ServletContext object, and the attributeReplaced() method is invoked when an attribute in the ServletContext object is replaced.

Note that Attribute changes may occur concurrently. It is your responsibility to ensure that accesses to a common resource are synchronized.

Page Counter Application
As another example, consider an application that provides a page counter for a servlet. The servlet increments a counter each time it is requested, thus the name page counter. The value of the counter is stored as an attribute of the ServletContext object so that it can be accessed from any servlet in the application. However, we also want the value to be written to a file so that the value will not be lost if the servlet/JSP container crashes. The file that stores the counter value is named counter.txt.

The application comprises two classes. The first is a listener class named AppAttributeEventDemo (see Listing 3), and the second is a servlet called PageCounterServlet (see Listing 4).

The AppAttributeEventDemo implements both ServletContextListener and ServletContextAttributeListener interfaces. In the contextInitialized() method, we write the code that will read the counter value from the counter.txt file and use this value to initialize a ServletContext attribute named pageCounter. You need to obtain the ServletContext object from the ServletContextEvent object passed to the contextInitialized() method.

Each time PageCounterServlet is requested, it increments the value of pageCounter. When this happens, the attributeReplaced() method of the listener class is triggered. In this method, we write the code that writes the value of pageCounter to the counter.txt file.

Because multiple users can call the servlet simultaneously, we pass the call to attributeReplaced() to a synchronized method, called writeCounter(). Here is where you write the code to write pageCounter to the file.

After you compile these two classes, open your browser and type in the URL for the servlet. You should see an image like Figure 1. The deployment descriptor for the example is given in Listing 5. Note that attributeReplaced() is also called when attributeAdded() is triggered.

Session Events
At the session level, you can enjoy a feature similar to the one added to the application level. An event occurs when a new HttpSession object is created for a user. An event occurs also when an HttpSession object is destroyed. In addition, events are triggered when an attribute in a HttpSession object is added, deleted, or modified.

The javax.servlet.http package provides two interfaces that you can implement to listen to HttpSession events: HttpSessionListener and HttpSessionAttribute-
Listener. The first enables you to listen to a session's lifecycle events (for example, the event that gets triggered when an HttpSession object is created and the event that is raised when an HttpSession object is destroyed).

The HttpSessionListener interface has two methods: sessionCreated() and sessionDestroyed():

public void sessionCreated(
   HttpSessionEvent se)
public void sessionDestroyed(
   HttpSessionEvent se)

The sessionCreated() method is called automatically when an HttpSession object is created. The sessionDestroyed() method is called when an HttpSession object is invalidated. Both methods will be passed an HttpSessionEvent class that you can use from inside the method. The HttpSessionEvent class is derived from the java.util.EventObject class. The HttpSessionEvent class defines one new method called getSession() that you can use to obtain the HttpSession object that changed.

User Counter Application
Let's create a counter that counts the number of users currently "in session." It creates an HttpSession object for a user the first time the user requests the servlet. If the user comes back to the same servlet, no HttpSession object will be created for the second time. Therefore, the counter is only incremented when an HttpSession object is created. However, an HttpSession object can be destroyed. When this happens, the counter must be decremented. Also, for the counter to be accessible by all users, it is stored as an attribute in the ServletContext object.

Our example has two classes: the listener class and a servlet that displays the counter value. The listener class is named SessionLifeCycleEventDemo and implements both the ServletContextListener interface and the HttpSessionListener interface (see Listing 6). You need the first interface so that you can create a ServletContext attribute and assign it an initial value of 0. As you can guess, you put this code in the contextInitialized() method:

public void contextInitialized(
   ServletContextEvent sce) {
   servletContext = 
      sce.getServletContext();
   servletContext.setAttribute((
      "userCounter"), 
   Integer.toString(counter));
}

Notice that the ServletContext object is assigned to a class variable servletContext, making the ServletContext object available from anywhere in the class.

The counter must be incremented when an HttpSession is created and decremented when an HttpSession is destroyed. Therefore, you need to provide implementations for both sessionCreated() and sessionDestroyed() methods:

public void sessionCreated(
   HttpSessionEvent hse) {
   System.out.println(
      "Session created.");
   incrementUserCounter();
}
public void sessionDestroyed(
   HttpSessionEvent hse) {
   System.out.println(
      "Session destroyed.");
   decrementUserCounter();
}

The sessionCreated() method calls the synchronized method incrementUserCounter(), and the sessionDestroyed() method calls the synchronized method decrementUserCounter(). The incrementUserCounter() method first obtains an attribute called userCounter from the ServletContext object, increments the counter, and stores the counter back to the userCounter attribute:

synchronized void 
   incrementUserCounter() {
   counter = Integer.parseInt(
      (String)servletContext.
      getAttribute("userCounter"));
   counter++;
   servletContext.setAttribute((
      "userCounter"),
      Integer.toString(counter));
   System.out.println(
      "User Count: " + counter);
}

The decrementUserCounter() method does the opposite. It first obtains the userCounter attribute from the ServletContext object, decrements the counter, and stores the counter back to the userCounter attribute:

synchronized void 
   decrementUserCounter() {
   int counter = 
      Integer.parseInt(
      (String)servletContext.
      getAttribute("userCounter"));
   counter�;
   servletContext.setAttribute(
      "userCounter"),
      Integer.toString(counter));
   System.out.println(
      "User Count: " + counter);
}

The second class of the application is the UserCounterServlet (available online at www.javapro.com). This servlet displays the value of the userCounter ServletContext attribute whenever someone requests the servlet. The code that does it is written in the doGet() method. When doGet() is invoked, you obtain the ServletContext object by calling the getServletContext() method:

ServletContext servletContext = 
   getServletContext();

Then try to obtain an HttpSession object from the HttpServletRequest object and create one if no HttpSession object exists:

HttpSession session = 
   request.getSession(true);

Next, get the userCounter attribute from the ServletContext object:

int userCounter = 0;
userCounter = Integer.parseInt(
   (String)servletContext.
   getAttribute("userCounter"));

For the example to work, you need to create a deployment descriptor as shown in Listing 7.

After you compile the two classes, open your browser and type in the URL to the servlet. You should see something similar to Figure 2.

The second session event listener interface is HttpSessionAttributeListener. You can implement this interface if you need to listen to events related to session attributes. The interface provides three methods, attributeAdded(), attributeRemoved(), and attributeReplaced():

public void attributeAdded(
   HttpSessionBindingEvent sbe)
public void attributeRemoved(
   HttpSessionBindingEvent sbe)
public void attributeReplaced(
   HttpSessionBindingEvent sbe)

The attributeAdded() method is called when an attribute is added to an HttpSession object. The attributeRemoved() and attributeReplaced() methods are called when an HttpSession attribute is removed or replaced, respectively.

All the methods receive an HttpSessionBindingEvent object. The HttpSessionBindingEvent is derived from the HttpSessionEvent class, so it inherits the getSession() method. In addition, the HttpSessionBindingEvent class has two methods, getName() and getValue():

public String getName
public Object getValue

The getName() method returns the name of the attribute that is bound to an HttpSession or unbound from an HttpSession. The getValue() method returns the value of an HttpSession attribute that has been added, removed, or replaced.

I hope these examples have provided a satisfactory introduction and have shown how these events can help you make effective servlet and JSP applications.

About the Author
Budi Kurniawan is an independent consultant in Sydney, Australia, and the author of Java Scalability with Servlet, JSP and EJB, to be published by NewRiders in April 2002. He is also one of the developers at BrainySoftware (www.brainysoftware.com). You can reach him at [email protected].