Java Servlet Programming, 2nd Edition > 3. The Servlet Lifecycle > 3.3 Init and Destroy |
3.3 Init and DestroyJust 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]
Depending on the server and the web application configuration, the init( ) method may be called at any of these times:
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 InitInit 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 Parametersimport 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.
3.3.2 A Counter with Init and DestroyUp 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 Counterimport 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.
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]
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.
|