ServletContextListener for Dogs

Using init parameters, both in the "config" (servlet specific) and "context" (container wide) flavor, has a few limits, such as: they are read only, they are only about Strings.

What if we want to share among all the servlet an object, and we want it to be sure that it has been initialized before any servlet could get access to it?

It is quite normal answering this question providing database access to our web application, but here, following the Head First guys suggestion, we'll do something less general, but sometimes more appropriate.

We could use an attribute, and initialize it using a ServletContextListener. In an attribute we could store whatever object we want, and the ServletContextListener provides us a way to to ensure a way to initialize it as first thing when the web application start (and to dispose it, when required, at its end).

So, let's roll. Here is the Dog class, that we want to initialize an instance of this class at web app startup and make available to all our servlets:
package ch05;
public class Dog {
    private String breed;

    public Dog(String breed) {
        this.breed = breed != null ? breed : "Unknown breed";
    }

    public String getBreed() {
        return breed;
    }
}
Very simple, indeed. It is constructed using a string, the dog's breed, and it has just a method, to return the dog's breed.

In the same package, ch05, let's create a servlet, DogTester, using the NetBeans wizard accepting the default values. We'll just modify slightly the standard processRequest() implementation to add a reference a Dog, stored as attribute in the servlet context as "dog":
package ch05;

// ...

public class DogTester extends HttpServlet {
    // ...
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        // ...
        out.println("<body>");
        out.println("<h1>Servlet DogTester at " + request.getContextPath() + "</h1>");

        Object o = getServletContext().getAttribute("dog"); // 1
        if(o == null || !(o instanceof Dog)) {
            out.println("Can't get dog information");
        }
        else {
            Dog dog = (Dog)o; // 2
            out.println("Dog's breed is: " + dog.getBreed());
        }
        out.println("</body>");
        out.println("</html>");
    }
    // ...
}
1. we try to get the attribute "dog" from the servlet context, if such an attribute is not available, or if it is not of the expected type (Dog) we give a feedback to the user;
2. we have ensured dog exists and it is a Dog instantiation, so we can safely downcast the atttribute, retrieve the dog's breed and give it to the user.

Since we let NetBeans doing all the standard work, we should find in our DD this information on our servlet:
<servlet>
    <servlet-name>DogTester</servlet-name>
    <servlet-class>ch05.DogTester</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>DogTester</servlet-name>
    <url-pattern>/DogTester</url-pattern>
</servlet-mapping>
So, we know we can run our servlet, at the address DogTester, under the folder assigned to our Web Application.

No one put a "dog" attribute yet in the servlet context, so we would be very surprised of getting something different from the missing dog information. But we are about to take care of this.

A ServletContextListener is not a normal servlet, so we won't use the NetBeans servlet wizard to create it, but we'll manage it as a "normal" java class, modifing by hand the code to let it implement the ServletContextListener interface. This should be the result of our job:
package ch05;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class DogListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext sc = sce.getServletContext();
        String dogBreed = sc.getInitParameter("breed");
        Dog d = new Dog(dogBreed);
        sc.setAttribute("dog", d);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // nothing to do
    }
}
To implement the ServletContextListener we have to override two methods called to initialize and to destroy this servlet.

As initialization we create a Dog object and put it in the servlet context as an attribute named "dog" (the one expected by the DogTester). To initialize our dog we use the context parameter "breed" that we'll add in a moment to the DD.

As destroy-time we don't have anything to do, here. Usually there should be some shutdown operation on the attribute to be performed.

Let's complete now our Deployment Descriptor to give a way to the container to find out how to call the ServletContextListener, and to define the "breed" parameter:
<listener>
    <listener-class>ch05.DogListener</listener-class>
</listener>
<context-param>
    <param-name>breed</param-name>
    <param-value>Great Dane</param-value>
</context-param>
Now, running the DogTester servlet we should get "Great Dane" as our dog breed.

No comments:

Post a Comment