3.3 Init and Destroy

Java Servlet Programming, 2nd Edition > 3. The Servlet Lifecycle > 3.3 Init and Destroy

 
< BACKCONTINUE >

3.3 Init and Destroy

Just like applets, servlets can define init( ) and destroy( ) methods. The server calls a servlet's init( ) method after the server constructs the servlet instance and before the servlet handles any requests. The server calls the destroy( ) method after the servlet has been taken out of service and all pending requests to the servlet have completed or timed out.[3]

[3] Early drafts of the upcoming Servlet API 2.3 specification promise to add additional lifecycle methods that allow servlets to listen when a context or session is created or shut down, as well as when an attribute is bound or unbound to a context or session.

Depending on the server and the web application configuration, the init( ) method may be called at any of these times:

  • When the server starts

  • When the servlet is first requested, just before the service( ) method is invoked

  • At the request of the server administrator

In any case, init( ) is guaranteed to be called and completed before the servlet handles its first request.

The init( ) method is typically used to perform servlet initialization creating or loading objects that are used by the servlet in the handling of its requests. During the init( ) method a servlet may want to read its initialization (init) parameters. These parameters are given to the servlet itself and are not associated with any single request. They can specify initial values, like where a counter should begin counting, or default values, perhaps a template to use when not specified by the request. Init parameters for a servlet are set in the web.xml deployment descriptor, although some servers have graphical interfaces for modifying this file. See Example 3-3.

Example 3-3. Setting init Parameters in the Deployment Descriptor
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app     PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"     "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"> <web-app>     <servlet>         <servlet-name>             counter         </servlet-name>         <servlet-class>             InitCounter         </servlet-class>         <init-param>             <param-name>                 initial             </param-name>             <param-value>                 1000             </param-value>             <description>                 The initial value for the counter  <!-- optional -->             </description>         </init-param>     </servlet> </web-app>

Multiple <init-param> entries can be placed within the <servlet> tag. The existence of the <description> tag is optional and intended primarily for graphical tools. The full Document Type Definition for the web.xml file can be found in Appendix C.

In the destroy( ) method, a servlet should free any resources it has acquired that will not be garbage collected. The destroy( ) method also gives a servlet a chance to write out its unsaved cached information or any persistent information that should be read during the next call to init( ).

3.3.1 A Counter with Init

Init parameters can be used for anything. In general, they specify initial values or default values for servlet variables, or they tell a servlet how to customize its behavior in some way. Example 3-4 extends our SimpleCounter example to read an init parameter (named initial) that stores the initial value for our counter. By setting the initial count to a high value, we can make our page appear more popular than it really is.

Example 3-4. A Counter That Reads init Parameters
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class InitCounter extends HttpServlet {   int count;   public void init() throws ServletException {     String initial = getInitParameter("initial");     try {       count = Integer.parseInt(initial);     }     catch (NumberFormatException e) {       count = 0;     }   }   public void doGet(HttpServletRequest req, HttpServletResponse res)                            throws ServletException, IOException {     res.setContentType("text/plain");     PrintWriter out = res.getWriter();     count++;     out.println("Since loading (and with a possible initialization");     out.println("parameter figured in), this servlet has been accessed");     out.println(count + " times.");   } }

The init( ) method uses the getInitParameter( ) method to get the value for the init parameter named initial. This method takes the name of the parameter as a String and returns the value as a String. There is no way to get the value as any other type. This servlet therefore converts the String value to an int or, if there's a problem, defaults to a value of 0. Remember, if you test this example you may need to restart your server for the web.xml changes to take effect, and you need to refer to the servlet using its registered name.

What Happened to super.init(config)?

In Servlet API 2.0, a servlet implementing the init( ) method had to implement a form of init( ) that took a ServletConfig parameter and had to call super.init(config) first thing:

public void init(ServletConfig config) throws ServletException {   super.init(config);   // Initialization code follows }

The ServletConfig parameter provided configuration information to the servlet, and the super.init(config) call passed the config object to the GenericServlet superclass where it was stored for use by the servlet. Specifically, the GenericServlet class used the passed-in config parameter to implement the ServletConfig interface itself (passing all calls to the delegate config), thus allowing a servlet to invoke ServletConfig methods on itself for convenience.

The whole operation was fairly convoluted, and in Servlet API 2.1 it was simplified so that a servlet now needs only to implement the init( ) no-argument version and the ServletConfig and GenericServlet handling will be taken care of in the background.

Behind the scenes, the GenericServlet class supports the no-arg init( ) method with code similar to this:

public class GenericServlet implements Servlet, ServletConfig {   ServletConfig _config = null;   public void init(ServletConfig config) throws ServletException {     _config = config;     log("init called");     init();   }   public void init() throws ServletException { }   public String getInitParameter(String name) {     return _config.getInitParameter(name);   }   // etc... }

Notice the web server still calls a servlet's init(ServletConfig config) method at initialization time. The change in 2.1 is that GenericServlet now passes on this call to the no-arg init(), which you can override without worrying about the config.

If backward compatibility is a concern, you should continue to override init(ServletConfig config) and call super.init(config). Otherwise you may end up wondering why your no-arg init() method is never called.

As a side note to this sidebar, some programmers find it useful to call super.destroy() first thing when implementing destroy(). This calls the GenericServlet implementation of destroy(), which writes a note to the log that the servlet is being destroyed.

3.3.2 A Counter with Init and Destroy

Up until now, the counter examples have demonstrated how servlet state persists between accesses. This solves only part of the problem. Every time the server is shut down or the servlet is reloaded, the count begins again. What we really want is persistence across loads a counter that doesn't have to start over.

The init( ) and destroy( ) pair can accomplish this. Example 3-5 further extends the InitCounter example, giving the servlet the ability to save its state in destroy( ) and load the state again in init( ). To keep things simple, assume this servlet is not registered and is accessed only as http://server:port/servlet/InitDestroyCounter. If it were registered under different names, it would have to save a separate state for each name.

Example 3-5. A Fully Persistent Counter
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class InitDestroyCounter extends HttpServlet {   int count;   public void init() throws ServletException {     // Try to load the initial count from our saved persistent state     FileReader fileReader = null;     BufferedReader bufferedReader = null;     try {       fileReader = new FileReader("InitDestroyCounter.initial");       bufferedReader = new BufferedReader(fileReader);       String initial = bufferedReader.readLine();       count = Integer.parseInt(initial);       return;     }     catch (FileNotFoundException ignored) { }  // no saved state     catch (IOException ignored) { }            // problem during read     catch (NumberFormatException ignored) { }  // corrupt saved state     finally {       // Make sure to close the file       try {         if (bufferedReader != null) {           bufferedReader.close();         }       }       catch (IOException ignored) { }     }     // No luck with the saved state, check for an init parameter     String initial = getInitParameter("initial");     try {       count = Integer.parseInt(initial);       return;     }     catch (NumberFormatException ignored) { }  // null or non-integer value     // Default to an initial count of "0"     count = 0;   }   public void doGet(HttpServletRequest req, HttpServletResponse res)                                throws ServletException, IOException {     res.setContentType("text/plain");     PrintWriter out = res.getWriter();     count++;     out.println("Since the beginning, this servlet has been accessed " +                 count + " times.");   }   public void destroy() {     super.destroy();  // entirely optional     saveState();   }   public void saveState() {     // Try to save the accumulated count     FileWriter fileWriter = null;     PrintWriter printWriter = null;     try {       fileWriter = new FileWriter("InitDestroyCounter.initial");       printWriter = new PrintWriter(fileWriter);       printWriter.println(count);       return;     }     catch (IOException e) {  // problem during write       // Log the exception. See Chapter 5.     }     finally {       // Make sure to close the file       if (printWriter != null) {         printWriter.close();       }     }   } }

Each time this servlet is unloaded, it saves its state in a file named InitDestroyCounter.initial. In the absence of a supplied path, the file is saved in the server process's current directory, usually the startup directory. Ways to specify alternate locations are discussed in Chapter 4.[4] This file contains a single integer, saved as a string, that represents the latest count.

[4] The location of the current user directory can be found with System.getProperty("user.dir").

Each time the servlet is loaded, it tries to read the saved count from the file. If, for some reason, the read fails (as it does the first time the servlet runs because the file doesn't yet exist), the servlet checks if an init parameter specifies the starting count. If that too fails, it starts fresh with zero. You can never be too careful in init( ) methods.

Servlets can save their state in many different ways. Some may use a custom file format, as was done here. Others may save their state as serialized Java objects or put it into a database. Some may even perform journaling, a technique common to databases and tape backups, where the servlet's full state is saved infrequently while a journal file stores incremental updates as things change. Which method a servlet should use depends on the situation. In any case, you should always be watchful that the state being saved isn't undergoing any change in the background.

Right now you're probably asking yourself, "What happens if the server crashes?" It's a good question. The answer is that the destroy( ) method will not be called.[5]

[5] Unless you're so unlucky that your server crashes while in the destroy( ) method. In that case, you may be left with a partially written state file garbage written on top of your previous state. To be perfectly safe, a servlet should save its state to a temporary file and then copy that file on top of the official state file in one command.

This doesn't cause a problem for destroy( ) methods that only have to free resources; a rebooted server does that job just as well (if not better). But it does cause a problem for a servlet that needs to save its state in its destroy( ) method. For these servlets, the only guaranteed solution is to save state more often. A servlet may choose to save its state after handling each request, such as a "chess server" servlet should do, so that even if the server is restarted, the game can resume with the latest board position. Other servlets may need to save state only after some important value has changed a "shopping cart" servlet needs to save its state only when a customer adds or removes an item from her cart. Last, for some servlets, it's fine to lose a bit of the recent state changes. These servlets can save state after some set number of requests. For example, in our InitDestroyCounter example, it should be satisfactory to save state every 10 accesses. To implement this, we can add the following line at the end of doGet( ):

if (count % 10 == 0) saveState();

Does this addition make you cringe? It should. Think about synchronization issues. We've opened up the possibility for data loss if saveState( ) is executed by two threads at the same time and the possibility for saveState( ) to not be called at all if count is incremented by several threads in a row before the check. Note that this possibility did not exist when saveState( ) was called only from the destroy( ) method: the destroy( ) method is called just once per servlet instance. Now that saveState( ) is called in the doGet( ) method, however, we need to reconsider. If by some chance this servlet is accessed so frequently that it has more than 10 concurrently executing threads, it's likely that two servlets (10 requests apart) will be in saveState( ) at the same time. This may result in a corrupted datafile. It's also possible the two threads will increment count before either thread notices it was time to call saveState( ). The fix is easy: move the count check into the synchronized block where count is incremented:

int local_count; synchronized(this) {   local_count = ++count;   if (count % 10 == 0) saveState(); } out.println("Since loading, this servlet has been accessed " +             local_count + " times.");

The moral of the story is harder: always be vigilant to protect servlet code from multithreaded access problems.


Last updated on 3/20/2003
Java Servlet Programming, 2nd Edition, © 2001 O'Reilly

< BACKCONTINUE >


Java servlet programming
Java Servlet Programming (Java Series)
ISBN: 0596000405
EAN: 2147483647
Year: 2000
Pages: 223

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net