Chapter 9

Listeners

The Servlet API comes with a set of event classes and listener interfaces for event-driven programming in servlet/JSP applications. All event classes are derived from java.util.Event and listeners are available in three different levels, the ServletContext level, the HttpSession level, and the ServletRequest level.

This chapter demonstrates how to write listeners and use them in servlet/JSP applications.

Listener Interfaces and Registration

The listener interfaces for creating listeners are part of the javax.servlet and javax.servlet.http packages. They are listed below.

  • javax.servlet.ServletContextListener. A listener to respond to servlet context lifecycle events. One of its method is called right after the servlet context is created and another method right before the servlet context is shut down.
  • javax.servlet.ServletContextAttributeListener. A listener that can act upon a servlet context attribute being added, removed, or replaced.
  • javax.servlet.http.HttpSessionListener. A listener to respond to the creation, timing-out, and invalidation of an HttpSession.
  • javax.servlet.http.HttpSessionAttributeListener. A listener that gets called when a session attribute is added, removed, or replaced.
  • javax.servlet.http.HttpSessionActivationListener. A listener that gets called when an HttpSession has been activated or passivated.
  • javax.servlet.http.HttpSessionBindingListener. A class whose instances are to be stored as HttpSession attributes may implement this interface. An instance of a class implementing HttpSessionBindingListener will get a notification when it is added to or removed from the HttpSession.
  • javax.servlet.ServletRequestListener. A listener to respond to the creation and removal of a ServletRequest.
  • javax.servlet.ServletRequestAttributeListener. A listener whose methods get called when an attribute has been added, removed, or replaced from a ServletRequest.

In addition, there are also AsyncListener, ReadListener and WriteListener for working with asynchronous operations. They are discussed in Chapter 18, “Asynchronous Operations.”

To create a listener, simply create a Java class that implements the relevant interface. In Servlet 3.0 and later there are two ways to register a listener so that it will be recognized by the servlet container. The first one is to use the WebListener annotation type like this:

@WebListener
public class ListenerClass implements ListenerInterface {

}

The second way to register a listener is by using a listener element in the deployment descriptor.

</listener>
    <listener-class>fully-qualified listener class</listener-class>
</listener>

You can have as many listeners as you want in your application. Note that calls to a listener method are performed synchronously.

Servlet Context Listeners

There are two listener interfaces at the ServletContext level, ServletContextListener and ServletContextAttributeListener. Both are explained in the sections below.

ServletContextListener

A ServletContextListener responds to the initialization and destruction of the ServletContext. When the ServletContext is initialized, the servlet container calls the contextInitialized method on all registered ServletContextListeners. Its signature is as follows.

void contextInitialized(ServletContextEvent event)

When the ServletContext is about to be decommissioned and destroyed, the servlet container calls the contextDestroyed method on all registered ServletContextListeners. Here is the signature of contextDestroyed.

void contextDestroyed(ServletContextEvent event)

Both contextInitialized and contextDestroyed receive a ServletContextEvent from the servlet container. A descendant of java.util.EventObject, the javax.servlet.ServletContextEvent class defines a getServletContext method that returns the ServletContext:

ServletContext getServletContext()

This method is important as this is the only easy way to access the ServletContext. Many ServletContextListeners are there to store an attribute in the ServletContext.

As an example, consider the listenerdemo application that accompanies this book. The AppListener class in Listing 9.1 is a ServletContextListener that places a Map containing country codes and names as a ServletContext attribute right after the ServletContext is initialized.

Listing 9.1: The AppListener class

package listenerdemo.listener;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class AppListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        
        Map<String, String> countries = 
                new HashMap<String, String>();
        countries.put("ca", "Canada");
        countries.put("us", "United States");
        servletContext.setAttribute("countries", countries);
    }
}

Pay attention to the implementation of the contextInitialized method in the class in Listing 9.1. It starts by calling the getServletContext method on the ServletContextEvent passed by the servlet container. It then creates a Map an populates it with two countries and puts the Map as a ServletContext attribute. In real world applications, data stored in a ServletContext may come from a database.

To test the listener, use the countries.jsp page in Listing 9.2.

Listing 9.2: The countries.jsp page

<!DOCTYPE html>
<html>
<head>
<title>Country List</title>
</head>
<body>
We operate in these countries:
<ul>
    <!--${countries.values().stream().map(x -> 
           "--><li>" += x += "</li><!--").toList()}-->
</ul>
</body>
</html>

The countries.jsp page uses the JSTL forEach tag to iterate over the countries map. Note that you need the JSTL libraries in your WEB-INF/lib directory of the app08a application for this example to work.

After you copy the JSTL libraries to the lib directory, restart your servlet container and direct your browser to this URL:

http://localhost:8080/listenerdemo/countries

You should see a screen similar to Figure 9.1.

Figure 9.1: Using ServletContextListener to load initial values

ServletContextAttributeListener

An implementation of ServletContextAttributeListener receives notification whenever an attribute is added to, removed from, or replaced in the ServletContext. Here are the three methods defined in this listener interface.

void attributeAdded(ServletContextAttributeEvent event)
void attributeRemoved(ServletContextAttributeEvent event)
void attributeReplaced(ServletContextAttributeEvent event)

The attributeAdded method is called by the servlet container when an attribute is added to the ServletContext. The attributeRemoved method gets invoked when an attribute is removed from the ServletContext and the attributeReplaced method gets called when a ServletContext attribute is replaced by a new one. All the listener methods received an instance of ServletContextAttributeEvent from which you can retrieve the attribute name and value.

The ServletContextAttributeEvent class is derived from ServletContextAttribute and adds these two methods to retrieve the attribute name and value, respectively.

java.lang.String getName()
java.lang.Object getValue()

Session Listeners

There are four HttpSession-related listener interfaces, HttpSessionListener, HttpSessionActivationListener, HttpSessionAttributeListener, and HttpSessionBindingListener. All these interfaces are members of the javax.servlet.http package and are explained in the sections below.

HttpSessionListener

The servlet container calls all registered HttpSessionListeners when an HttpSession is created or destroyed. The two methods defined in HttpSessionListener are sessionCreated and sessionDestroyed.

void sessionCreated(HttpSessionEvent event)
void sessionDestroyed(HttpSessionEvent event)

Both methods receive an instance of HttpSessionEvent, which is a descendant of java.util.Event. You can call the getSession method on the HttpSessionEvent to obtain the HttpSession created or destroyed. The signature of the getSession method is as follows:

HttpSession getSession()

As an example, look at the SessionListener class in listenerdemo, which is printed in Listing 9.3. This listener provides the number of HttpSessions in the application. An AtomicInteger is used as a counter and stored as a ServletContext attribute. When an HttpSession is created, this counter is incremented. When an HttpSession is invalidated, this counter is decremented. As such, it provides an accurate snapshot of the number of users having a valid session at the time the counter is read. An AtomicInteger is used instead of an Integer to guarantee the atomicity of the incrementing and decrementing operations.

Listing 9.3: The SessionListener class

package listenerdemo.listener;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener
public class SessionListener implements HttpSessionListener,
        ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        servletContext.setAttribute("userCounter", 
                new AtomicInteger());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    }

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        ServletContext servletContext = session.getServletContext();
        AtomicInteger userCounter = (AtomicInteger) servletContext
                .getAttribute("userCounter");
        int userCount = userCounter.incrementAndGet();
        System.out.println("userCount incremented to :" + 
                    userCount);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        ServletContext servletContext = session.getServletContext();
        AtomicInteger userCounter = (AtomicInteger) servletContext
                .getAttribute("userCounter");
        int userCount = userCounter.decrementAndGet();
        System.out.println("---------- userCount decremented to :"
                + userCount);
    }
}

As you can see in Listing 9.3, the SessionListener class implements ServletContextListener and HttpSessionListener. Therefore, you need to implement methods from both interfaces.

The contextInitialized method is inherited from the ServletContextListener interface and it creates and stores an AtomicInteger in the ServletContext. Since the initial value of an AtomicInteger is zero, it indicates that at the time the application is started there is zero user. The name of the ServletContext attribute is userCounter.

    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        servletContext.setAttribute("userCounter", 
                new AtomicInteger());
    }

The sessionCreated method is invoked every time an HttpSession is created. What this method does is retrieve the HttpSession created and obtains the userCounter attribute from the ServletContext. It then calls the incrementAndGet method on the userCounter AtomicInteger. The value is printed to make it easy for you to see how the listener works.

    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        ServletContext servletContext = session.getServletContext();
        AtomicInteger userCounter = (AtomicInteger) servletContext
                .getAttribute("userCounter");
        int userCount = userCounter.incrementAndGet();
        System.out.println("userCount incremented to :" + 
                    userCount);
    }

The sessionDestroyed method is called right before an HttpSession is about to be destroyed. The method implementation is similar to that of sessionCreated, except that it decrements the value of userCounter instead of incrementing is.

    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        ServletContext servletContext = session.getServletContext();
        AtomicInteger userCounter = (AtomicInteger) servletContext
                .getAttribute("userCounter");
        int userCount = userCounter.decrementAndGet();
        System.out.println("---------- userCount decremented to :"
                + userCount);
    }

To test the listener, request the countries.jsp page again using different browsers and watch what’s printed on your console. Here is the URL to invoke countries.jsp:

http://localhost:8080/listenerdemo/countries.jsp

The first invocation will cause this printed in your console:

userCount incremented to :1

Another request from the same browser won’t change the value of userCounter as it will be associated with the same HttpSession. However, invoking the page using a different browser will increase the value of userCounter.

Now, if you have time wait until the HttpSessions expire and take note of what is printed on the console again.

HttpSessionAttributeListener

An HttpSessionAttributeListener is like a ServletContextAttributeListener, except that it gets invoked when an attribute is added to, removed from, or replaced in an HttpSession. The following are methods defined in the HttpSessionAttributeListener interface.

void attributeAdded(HttpSessionBindingEvent event)
void attributeRemoved( HttpSessionBindingEvent event)
void attributeReplaced( HttpSessionBindingEvent event)

The attributeAdded method is called by the servlet container when an attribute is added to the HttpSession. The attributeRemoved method gets invoked when an attribute is removed from the HttpSession and the attributeReplaced method gets called when a HttpSession attribute is replaced by a new one. All the listener methods received an instance of HttpSessionBindingEvent from which you can retrieve the corresponding HttpSession and the attribute name and value.

java.lang.String getName()
java.lang.Object getValue()

Since HttpSessionBindingEvent is a subclass of HttpSessionEvent, you can also obtain the affected HttpSession in your HttpSessionAttributeListener class.

HttpSessionActivationListener

In a distributed environment where multiple servlet containers are configured to scale, the servlet containers may migrate or serialize session attributes in order to conserve memory. Typically, relatively rarely accessed object may be serialized into secondary storage when memory is low. When doing so, the servlet containers notify session attributes whose classes implement the HttpSessionActivationListener interface.

There are two methods defined in this interface, sessionDidActivate and sessionWillPassivate:

void sessionDidActivate(HttpSessionEvent event)
void sessionWillPassivate(HttpSessionEvent event)

The sessionDidActivate method is invoked after the HttpSession containing this object has just been activated. The HttpSessionEvent the servlet container passes to the method lets you obtain the HttpSession that was activated.

The sessionWillPassivate method is called when the HttpSession containing this listener is about to be passivated. Like sessionDidActivate, the servlet container passes an HttpSessionEvent to the method so that the session attribute may act on the HttpSession.

HttpSessionBindingListener

An HttpSessionBindingListener gets notification when it is bound and unbound to an HttpSession. A class whose instances are to be stored as session attributes may implement this interface if knowing when it’s bound to or unbound from an HttpSession is of interest. For example, an object whose class implements this interface might update itself when it is stored as an HttpSession attribute. Or, an implementation of HttpSessionBindingListener might release resources it is holding once it’s unbound from the HttpSession.

As an example, the Product class in Listing 9.4 implements HttpSessionBindingListener.

Listing 9.4: A class implementing HttpSessionBindingListener

package listenerdemo.model;
import java.math.BigDecimal;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

public class Product implements HttpSessionBindingListener {
    private String id;
    private String name;
    private BigDecimal price;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    
    @Override
    public void valueBound(HttpSessionBindingEvent event) {
        String attributeName = event.getName();
        System.out.println(attributeName + " valueBound");
    }
    
    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        String attributeName = event.getName();
        System.out.println(attributeName + " valueUnbound");
    }
}

The listener does not do more than printing to the console when it’s bound to an HttpSession and when it’s unbound.

ServletRequest Listeners

There are three listener interfaces at the ServletRequest level, ServletRequestListener, ServletRequestAttributeListener, and AsyncListener. The first two listeners are discussed in this section and AsyncListener is covered in Chapter 18, “Asynchronous Operations.”

ServletRequestListener

A ServletRequestListener responds to the creation and destruction of a ServletRequest. In a servlet container that pools and reuses ServletRequests, the creation of a ServletRequest is taken to be the time it is retrieved from the pool and its destruction the time it is returned to the pool.

The ServletRequestListener interface defines two methods, requestInitialized and requestDestroyed. Their signatures are as follows.

void requestInitialized(ServletRequestEvent event)
void requestDestroyed(ServletRequestEvent event)

The requestInitialized method is invoked when a ServletRequest has been created (or taken from the pool) and the requestDestroyed method when a ServletRequest is about to be destroyed (or returned to the pool). Both methods receive a ServletRequestEvent, from which you can retrieve the corresponding ServletRequest instance by calling the getServletRequest method.

ServletRequest getServletRequest()

In addition, the ServletRequestEvent interface also defines the getServletContext method that returns the ServletContext. The method signature is as follows.

ServletContext getServletContext()

As an example, consider the PerfStatListener class in app08a. The listener measures the difference between the time the ServletRequest is destroyed and the time it was created, thus effectively the time taken to execute a request.

The PerfStatListener class in Listing 9.5 takes advantage of the ServletRequestListener interface to measure how long it takes an HTTP request to complete. It relies on the fact that the servlet container calls the requestInitialized method on a ServletRequestListener at the beginning of a request and calls the requestDestroyed method after it processes it. By reading the current time at the starts of the two events and compare the two, you can get the approximate of how long it took an HTTP request to complete.

Listing 9.5: The PerfStatListener

package listenerdemo.listener;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;

@WebListener
public class PerfStatListener implements ServletRequestListener {
    
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        servletRequest.setAttribute("start", System.nanoTime());
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        Long start = (Long) servletRequest.getAttribute("start");
        Long end = System.nanoTime();
        HttpServletRequest httpServletRequest = 
                (HttpServletRequest) servletRequest;
        String uri = httpServletRequest.getRequestURI();
        System.out.println("time taken to execute " + uri +
                ":"  + ((end - start) / 1000) + "microseconds");
    }
}

The requestInitialized method in Listing 9.5 calls System.nanoTime() and puts the return value (that will be boxed as Long) as a ServletRequest attribute.

    public void requestInitialized(ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        servletRequest.setAttribute("start", System.nanoTime());
    }

The nanoTime method returns a long indicating some arbitrary time. The return value is not related to any notion of system or wall-clock time, but two return values taken in the same JVM are perfect for measuring the time that elapsed between the first nanoTime call and the second.

As you may have guessed, the requestDestroyed method calls the nanoTime method the second time and compares its value with the first.

    public void requestDestroyed(ServletRequestEvent sre) {
        ServletRequest servletRequest = sre.getServletRequest();
        Long start = (Long) servletRequest.getAttribute("start");
        Long end = System.nanoTime();
        HttpServletRequest httpServletRequest = 
                (HttpServletRequest) servletRequest;
        String uri = httpServletRequest.getRequestURI();
        System.out.println("time taken to execute " + uri +
                ":"  + ((end - start) / 1000) + "microseconds");
    }

To test the PerfStatListener, invoke the countries.jsp page in app08a again.

ServletRequestAttributeListener

A ServletRequestAttributeListener gets called when an attribute has been added to, removed from, or replaced in a ServletRequest. There are three methods defined in the ServletRequestAttributeListener interface, attributeAdded, attributeReplaced, and attributeRemoved. The signatures of the methods are as follows.

void attributeAdded(ServletRequestAttributeEvent event)
void attributeRemoved(ServletRequestAttributeEvent event)
void attributeReplaced(ServletRequestAttributeEvent event)

All the methods receive an instance of ServletRequestAttributeEvent, which is a child class of ServletRequestEvent. The ServletRequestAttributeEvent class exposes the related attribute through the getName and getValue methods:

java.lang.String getName()
java.lang.Object getValue()

Summary

In this chapter you’ve learned the various listener types that can be found in the Servlet API. Those listeners may fall into one of three scopes: application, session, and request. Putting listeners to work is also straightforward, involving a simple registration process. You can register a listener by annotating the implementation class using @WebListener or by using the listener element in the deployment descriptor.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.189.177