Servlet 2.3: New features exposed

A full update on the latest Servlet API spec

On Oct. 20, 2000, Sun Microsystems published the "Proposed Final Draft" of the Servlet API 2.3 specification. (See Resources for a link to the formal specification.) Although the spec was published by Sun, Servlet API 2.3 was actually developed by the many individuals and companies working on the JSR-053 expert group, in accordance with the Java Community Process (JCP) 2.0. Danny Coward of Sun Microsystems led the servlet expert group.

The specification is not quite finished; the Proposed Final Draft is one step away from a formal Final Release, and technical details are still subject to change. However, those changes should not be significant -- in fact, server vendors have already begun to implement the new features. That means now is a good time to start learning about what's coming in Servlet API 2.3.

In this article, I will describe in detail everything that changed between API 2.2 and API 2.3. I will also explain the reasons for the changes and demonstrate how to write servlets using the new features. To keep the article focused, I will assume you're familiar with the classes and methods of previous versions of the Servlet API. If you're not, you can peruse the Resources section for links to sites (and my new book!) that will help get you up to speed.

Servlet API 2.3 actually leaves the core of servlets relatively untouched, which indicates that servlets have reached a high level of maturity. Most of the action has involved adding new features outside the core. Among the changes:

  • Servlets now require JDK 1.2 or later
  • A filter mechanism has been created (finally!)
  • Application lifecycle events have been added
  • New internationalization support has been added
  • The technique to express inter-JAR dependencies has been formalized
  • Rules for class loading have been clarified
  • New error and security attributes have been added
  • The HttpUtils class has been deprecated
  • Various new helpful methods have been added
  • Several DTD behaviors have been expanded and clarified

Other clarifications have been made, but they mostly concern server vendors, not general servlet programmers (except for the fact that programmers will see improved portability), so I'll omit those details.

Before I begin my examination, let me point out that version 2.3 has been released as a draft specification only. Most of the features discussed here won't yet work with all servers. If you want to test those features, I recommend downloading the official reference implementation server, Apache Tomcat 4.0. It's open source, and you can download the server for free. Tomcat 4.0 is currently in beta release; its support for API 2.3 is getting better, but is still incomplete. Read the NEW_SPECS.txt file that comes with Tomcat 4.0 to learn its level of support for all new specification features. (See Resources for more information on Tomcat.)

Servlets in J2SE and J2EE

One of the first things you should note about Servlet API 2.3 is that servlets now depend on the Java 2 Platform, Standard Edition 1.2 (also known as J2SE 1.2 or JDK 1.2). This small, but important, change means you can now use J2SE 1.2 features in your servlets and be guaranteed that the servlets will work across all servlet containers. Previously, you could use J2SE 1.2 features, but servers were not required to support them.

The Servlet API 2.3 is slated to become a core part of Java 2 Platform, Enterprise Edition 1.3 (J2EE 1.3). The previous version, Servlet API 2.2, was part of J2EE 1.2. The only noticeable difference is the addition of a few relatively obscure J2EE-related deployment descriptor tags in the web.xml DTD: <resource-env-ref> to support "administered objects," such as those required by the Java Messaging System (JMS); <res-ref-sharing-scope> to allow either shared or exclusive access to a resource reference; and <run-as> to specify the security identity of a caller to an EJB. Most servlet authors need not concern themselves with those J2EE tags; you can get a full description from the J2EE 1.3 specification.

Filters

The most significant part of API 2.3 is the addition of filters -- objects that can transform a request or modify a response. Filters are not servlets; they do not actually create a response. They are preprocessors of the request before it reaches a servlet, and/or postprocessors of the response leaving a servlet. In a sense, filters are a mature version of the old "servlet chaining" concept. A filter can:

  • Intercept a servlet's invocation before the servlet is called
  • Examine a request before a servlet is called
  • Modify the request headers and request data by providing a customized version of the request object that wraps the real request
  • Modify the response headers and response data by providing a customized version of the response object that wraps the real response
  • Intercept a servlet's invocation after the servlet is called

You can configure a filter to act on a servlet or group of servlets; that servlet or group can be filtered by zero or more filters. Practical filter ideas include authentication filters, logging and auditing filters, image conversion filters, data compression filters, encryption filters, tokenizing filters, filters that trigger resource access events, XSLT filters that transform XML content, or MIME-type chain filters (just like servlet chaining).

A filter implements javax.servlet.Filter and defines its three methods:

  • void setFilterConfig(FilterConfig config): Sets the filter's configuration object
  • FilterConfig getFilterConfig(): Returns the filter's configuration object
  • void doFilter(ServletRequest req, ServletResponse res, FilterChain chain): Performs the actual filtering work

The server calls setFilterConfig() once to prepare the filter for service, then calls doFilter() any number of times for various requests. The FilterConfig interface has methods to retrieve the filter's name, its init parameters, and the active servlet context. The server passes null to setFilterConfig() to indicate that the filter is being taken out of service.

Each filter receives in its doFilter() method the current request and response, as well as a FilterChain containing the filters that still must be processed. In the doFilter() method, a filter may do what it wants with the request and response. (It could gather data by calling their methods, or wrap the objects to give them new behavior, as discussed below.) The filter then calls chain.doFilter() to transfer control to the next filter. When that call returns, a filter can, at the end of its own doFilter() method, perform additional work on the response; for instance, it can log information about the response. If the filter wants to halt the request processing and gain full control of the response, it can intentionally not call the next filter.

A filter may wrap the request and/or response objects to provide custom behavior, changing certain method call implementation to influence later request handling actions. API 2.3 provides new HttpServletRequestWrapper and HttpServletResponseWrapper classes to help with this; they provide default implementations of all request and response methods, and delegate the calls to the original request or response by default. That means changing one method's behavior requires just extending the wrapper and reimplementing one method. Wrappers give filters great control over the request-handling and response-generating process. The code for a simple logging filter that records the duration of all requests is shown below:

public class LogFilter implements Filter {
  FilterConfig config;
  public void setFilterConfig(FilterConfig config) {
    this.config = config;
  }
  public FilterConfig getFilterConfig() {
    return config;
  }
  public void doFilter(ServletRequest req,
                       ServletResponse res,
                       FilterChain chain) {
    ServletContext context = getFilterConfig().getServletContext();
    long bef = System.currentTimeMillis();
    chain.doFilter(req, res);  // no chain parameter needed here
    long aft = System.currentTimeMillis();
    context.log("Request to " + req.getRequestURI() + ": " + (aft-bef));
  }
}

When the server calls setFilterConfig(), the filter saves a reference to the config in its config variable, which is later used in the doFilter() method to retrieve the ServletContext. The logic in doFilter() is simple; time how long request handling takes and log the time once processing has completed. To use this filter, you must declare it in the web.xml deployment descriptor using the <filter> tag, as shown below:

<filter>
  <filter-name>
    log
  </filter-name>
  <filter-class>
    LogFilter
  </filter-class>
</filter>

This tells the server a filter named log is implemented in the LogFilter class. You can apply a registered filter to certain URL patterns or servlet names using the <filter-mapping> tag:

<filter-mapping>
  <filter-name>log</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

This configures the filter to operate on all requests to the server (static or dynamic), just what we want for our logging filter. If you connect to a simple page, the log output might look like this:

Request to /index.jsp: 10

Lifecycle events

Servlet API 2.3's second most significant change is the addition of application lifecycle events, which let "listener" objects be notified when servlet contexts and sessions are initialized and destroyed, as well as when attributes are added or removed from a context or session.

Servlet lifecycle events work like Swing events. Any listener interested in observing the ServletContext lifecycle can implement the ServletContextListener interface. The interface has two methods:

  • void contextInitialized(ServletContextEvent e): Called when a Web application is first ready to process requests (i.e. on Web server startup and when a context is added or reloaded). Requests will not be handled until this method returns.
  • void contextDestroyed(ServletContextEvent e): Called when a Web application is about to be shut down (i.e. on Web server shutdown or when a context is removed or reloaded). Request handling will be stopped before this method is called.

The ServletContextEvent class passed to those methods has only a getServletContext() method that returns the context being initialized or destroyed.

A listener interested in observing the ServletContext attribute lifecycle can implement the ServletContextAttributesListener interface, which has three methods:

  • void attributeAdded(ServletContextAttributeEvent e): Called when an attribute is added to a servlet context
  • void attributeRemoved(ServletContextAttributeEvent e): Called when an attribute is removed from a servlet context
  • void attributeReplaced(ServletContextAttributeEvent e): Called when an attribute is replaced by another attribute in a servlet context

The ServletContextAttributeEvent class extends ServletContextEvent, and adds getName() and getValue() methods so the listener can learn about the attribute being changed. That is useful because Web applications that need to synchronize application state (context attributes) with something like a database can now do it in one place.

The session listener model is similar to the context listener model. In the session model, there's an HttpSessionListener interface with two methods:

  • void sessionCreated(HttpSessionEvent e): Called when a session is created
  • void sessionDestroyed(HttpSessionEvent e): Called when a session is destroyed (invalidated)

The methods accept an HttpSessionEvent instance with a getSession() method to return the session being created or destroyed. You can use all these methods when implementing an admin interface that keeps track of all active users in a Web application.

The session model also has an HttpSessionAttributesListener interface with three methods. Those methods tell the listener when attributes change, and could be used, for example, by an application that synchronizes profile data held in sessions into a database:

  • void attributeAdded(HttpSessionBindingEvent e): Called when an attribute is added to a session
  • void attributeRemoved(HttpSessionBindingEvent e): Called when an attribute is removed from a session
  • void attributeReplaced(HttpSessionBindingEvent e): Called when an attribute replaces another attribute in a session

As you might expect, the HttpSessionBindingEvent class extends HttpSessionEvent and adds getName() and getValue() methods. The only somewhat abnormal thing is that the event class is named HttpSessionBindingEvent, not HttpSessionAttributeEvent. That's for legacy reasons; the API already had an HttpSessionBindingEvent class, so it was reused. This confusing aspect of the API may be ironed out before final release.

A possible practical use of lifecycle events is a shared database connection managed by a context listener. You declare the listener in the web.xml as follows:

 <listener>
  <listener-class>
    com.acme.MyConnectionManager
  </listener-class>
 </listener>

The server creates an instance of the listener class to receive events and uses introspection to determine what listener interface (or interfaces) the class implements. Bear in mind that because the listener is configured in the deployment descriptor, you can add new listeners without any code change. You could write the listener itself as something like this:

1 2 3 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more