@WebListener annotation
During the lifetime of a
typical web application, a number of events take place, such as HTTP
requests are created or destroyed, request or session attributes are
added, removed, or modified, and so on and so forth.
The Servlet API provides a
number of listener interfaces we can implement in order to react to
these events. All of these interfaces are in the javax.servlet package. The following table summarizes them:
Listener Interface
|
Description
|
---|
ServletContextListener
|
Contains methods for handling context initialization and destruction events.
|
ServletContextAttributeListener
|
Contains methods for reacting to any attributes added, removed, or replaced in the servlet context (application scope).
|
ServletRequestListener
|
Contains methods for handling request initialization and destruction events.
|
ServletRequestAttributeListener
|
Contains methods for reacting to any attributes added, removed, or replaced in the request.
|
HttpSessionListener
|
Contains methods for handling HTTP session initialization and destruction events.
|
HttpSessionAttributeListener
|
Contains methods for reacting to any attributes added, removed, or replaced in the HTTP session.
|
All we need to do to handle any
of the events handled by the interfaces described in this table is to
implement one of these interfaces and annotate it with the @WebListener interface or declare it in the web.xml deployment descriptor via the<listener>
tag. Unsurprisingly, the ability to use an annotation to register a
listener was introduced in version 3.0 of the Servlet specification.
The API for all
of these interfaces is fairly straightforward and intuitive. We will
show an example for one of these interfaces; others will be very
similar.
The following code example illustrates how to implement the ServletRequestListener interface, which can be used to perform some action whenever an HTTP request is created or destroyed:
package net.ensode.glassfishbook.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
@WebListener()
public class HttpRequestListener implements ServletRequestListener
{
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent)
{
ServletContext servletContext = servletRequestEvent.getServletContext();
servletContext.log("New request initialized");
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent)
{
ServletContext servletContext =
servletRequestEvent.getServletContext();
servletContext.log("Request destroyed");
}
}
As we can see, all we need to do to activate our listener class is to annotate it with the @WebListener
annotation. Our listener must also implement one of the listener
interfaces we listed previously. In our example, we chose to implement javax.servlet.ServletRequestListener. This interface has methods that are automatically invoked whenever an HTTP request is initialized or destroyed.
The ServletRequestListener interface has two methods: requestInitialized() and requestDestroyed().
In our previous simple implementation, we simply sent some output to
the log, but of course we can do anything we need to do in our
implementations.
We can see the following output in GlassFish's log:
[#|2009-10-03T10:37:53.465-0400|INFO|glassfish|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=39;
_ThreadName=Thread-2;|PWC1412: WebModule[/nbservlet30listener] ServletContext.log():New request initialized|#]
[#|2009-10-03T10:37:53.517-0400|INFO|glassfish|javax.enterprise.system.container.web.com.sun.enterprise.web|_ThreadID=39;
_ThreadName=Thread-2;|PWC1412: WebModule[/nbservlet30listener] ServletContext.log():Request destroyed|#]
Implementing the other listener interfaces is just as simple and straightforward.
Pluggability
When the original Servlet API
was released back in the late 1990s, writing servlets was the only way
of writing server-side web applications in Java. Since then, several
standard Java EE and third-party frameworks have been built on top of
the Servlet API. Examples of such standard frameworks include JSP's and
JSF, third-party frameworks include Struts, Wicket, Spring Web MVC, and
several others.
Nowadays, very few (if any)
Java web applications are built using the Servlet API directly.
Instead, the vast majority of projects utilize one of the several
available Java web application frameworks. All of these frameworks use
the Servlet API "under the covers". Therefore, setting up an application
to use one of these frameworks has always involved making some
configuration in the application's web.xml deployment descriptor. In some cases, some applications use more than one framework. This tends to make the web.xml deployment descriptor fairly large and hard to maintain.
Servlet 3.0
introduces the concept of pluggability. Web application framework
developers now have not one, but two ways to avoid having application
developers modify the web.xml deployment descriptor in order to use their framework. Framework developers can choose to use annotations instead of a web.xml
to configure their servlets. After doing this, all that is needed to
use the framework is to include the library JAR file(s) provided by the
framework developers in the application's WAR file. Alternatively,
framework developers may choose to include a web-fragment.xml file as part of the JAR file to be included in web applications that use their framework.
web-fragment.xml is almost identical to web.xml. The main difference is that the root element of a web-fragment.xml file is<web-fragment> as opposed to<web-app>. The following code example illustrates a sample web-fragment.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<web-fragment version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd">
<servlet>
<servlet-name>WebFragment</servlet-name>
<servlet-class>
net.ensode.glassfishbook.webfragment.WebFragmentServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WebFragment</servlet-name>
<url-pattern>/WebFragment</url-pattern>
</servlet-mapping>
</web-fragment>
As we can see, web-fragment.xml is almost identical to a typical web.xml. In this simple example we only use the<servlet> and<servlet-mapping> elements. However, all other usual web.xml elements such as<filter>, <filter-mapping>, and<listener> are available as well.
As specified in our web-fragment.xml file, our servlet can be invoked via its URL pattern, /WebFragment. Therefore, the URL to execute our servlet, once deployed as part of a web application, would be http://localhost:8080/webfragmentapp/WebFragment. Of course, the host name, port, and context root must be adjusted as appropriate.
All we need to do for GlassFish or any Java EE 6-compliant application server to pick up the settings in web-fragment.xml is to place the file in the META-INF folder of the library where we pack our servlet, filter, and/or listener, then place our library's JAR file in the lib directory of the WAR file containing our application.
Configuring web applications programmatically
In addition to allowing us to configure web applications through annotations and through a web-fragment.xml file, Servlet 3.0 also allows us to configure our web applications programmatically at runtime.
The ServletContext
class has new methods to configure servlets, filters, and listeners
programmatically. The following example illustrates how to configure a
servlet programmatically at runtime, without resorting to the @WebServlet annotation or to XML:
package net.ensode.glassfishbook.servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.WebListener;
@WebListener()
public class ServletContextListenerImpl implements ServletContextListener
{
@Override
public void contextInitialized(ServletContextEvent servletContextEvent)
{
ServletContext servletContext = servletContextEvent.getServletContext();
try
{
ProgrammaticallyConfiguredServlet servlet = servletContext. createServlet(ProgrammaticallyConfiguredServlet.class);
servletContext.addServlet("ProgrammaticallyConfiguredServlet", servlet);
ServletRegistration servletRegistration = servletContext.getServletRegistration( "ProgrammaticallyConfiguredServlet");
servletRegistration.addMapping( "/ProgrammaticallyConfiguredServlet");
}
catch (ServletException servletException)
{
servletContext.log(servletException.getMessage());
}
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent)
{
}
}
In this example, we invoke the createServlet() method of ServletContext to create the servlet that we are about to configure. This method takes an instance of java.lang.Class corresponding to our servlet's class. This method returns a class implementing javax.servlet.Servlet
or any of its child interfaces (thanks to Generics, a Java language
feature introduced in Java 5, we don't need to explicitly cast the
return value to the actual type of our servlet).
Once we create our servlet, we need to invoke addServlet() on our ServletContext instance to register our servlet with the servlet container. This method takes two parameters: the first being a String corresponding to the servlet name, the second being the servlet instance returned by a call to createServlet().
Once we have registered our servlet, we need to add a URL mapping to it. In order to do this, we need to invoke the getServletRegistration() method on our ServletContext instance, passing the servlet name as a parameter. This method returns the servlet container's implementation of javax.servlet.ServletRegistration. From this object, we need to invoke its addMapping() method, passing the URL mapping we wish our servlet to handle.
Our example servlet is very simple. It simply displays a text message on the browser.
package net.ensode.glassfishbook.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ProgrammaticallyConfiguredServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
ServletOutputStream outputStream = response.getOutputStream();
outputStream.println("This message was generated from a servlet that was " + "configured programmatically.");
}
}
After packing our code in a WAR file, deploying to GlassFish and pointing the browser to the appropriate URL (http://localhost:8080/programmaticservletwebapp/ProgrammaticallyConfiguredServlet, assuming we packaged the application in a WAR file named programmaticservletwebapp.war and didn't override the default context root), we should see the following message in the browser:
This message was generated from a servlet that was configured programmatically.
The ServletContext interface has methods to create and add servlet filters and listeners. They work very similarly to the way the addServlet() and createServlet() methods work, therefore we won't be discussing them in detail. Refer to the Java EE 6 API documentation at http://java.sun.com/javaee/6/docs/api/ for details.
Asynchronous processing
Traditionally, servlets have
created a single thread per request in Java web applications. After a
request is processed, the thread is made available for other requests to
use. This model works fairly well for traditional web applications in
which HTTP requests are relatively few and far in between. However, most
modern web applications take advantage of Ajax (Asynchronous JavaScript
and XML), a technique that makes web applications behave much more
responsively than traditional web applications. Ajax has the side effect
of generating a lot more HTTP requests than traditional web
applications. If some of these threads block for a long time waiting for
a resource to be ready or are doing anything that takes a long time to
process, it is possible our application may suffer from thread
starvation.
To alleviate the
situation described in the previous paragraph, the Servlet 3.0
specification introduced asynchronous processing. Using this new
capability, we are no longer limited to a single thread per request. We
can now spawn a separate thread and return the original thread back to
the pool to be reused by other clients.
The following example illustrates how to implement asynchronous processing using the new capabilities introduced in Servlet 3.0:
package net.ensode.glassfishbook.asynchronousservlet;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "AsynchronousServlet", urlPatterns = { "/AsynchronousServlet"}, asyncSupported = true)
public class AsynchronousServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
final Logger logger = Logger.getLogger(AsynchronousServlet.class.getName());
logger.log(Level.INFO, "--- Entering doGet()");
final AsyncContext ac = request.startAsync();
logger.log(Level.INFO, "---- invoking ac.start()");
ac.start(new Runnable()
{
@Override
public void run()
{
logger.log(Level.INFO, "inside thread");
try
{
//simulate a long running process.
Thread.sleep(10000);
}
catch (InterruptedException ex)
{
Logger.getLogger(AsynchronousServlet.class.getName()). log(Level.SEVERE, null, ex);
}
try
{
ac.getResponse().getWriter().println("You should see this after a brief wait");
ac.complete();
}
catch (IOException ex)
{
Logger.getLogger(AsynchronousServlet.class.getName()).
log(Level.SEVERE, null, ex);
}
}
});
logger.log(Level.INFO, "Leaving doGet()");
}
}
The first thing we need to do to make sure our asynchronous processing code works as expected is to set the asyncSupported attribute of the @WebServlet annotation to true.
To actually spawn an asynchronous process, we need to invoke the startAsync() method on the instance of HttpServletRequest that we receive as a parameter in the doGet() or doPost() method in our servlet. This method returns an instance of javax.servlet.AsyncContext. This class has a start() method that takes an instance of a class implementing java.lang.Runnable as its sole parameter. In our example, we used an anonymous inner class to implement Runnable in line. Of course, a standard Java class implementing Runnable can be used as well.
When we invoke the start() method of AsyncContext, a new thread is spawned and the run() method of the Runnable instance is executed. This thread runs in the background, the doGet()
method returns immediately, and the request thread is immediately
available to service other clients. It is important to notice that even
though the doGet() method returns
immediately, the response is not committed until after the thread
spawned finishes. It can signal it has finished processing by invoking
the complete() method on AsyncContext.
In
the previous example, we sent some entries to the GlassFish log file to
illustrate better what is going on. Observing the GlassFish log right
after our servlet executes, we should notice that all log entries are
written to the log within a fraction of a second of each other. The
message You should see this after a brief wait isn't shown in the browser until after the log entry indicating that we are leaving the doGet() method, gets written to the log.