| [ Team LiB ] |
|
21.2 Developing Classic Tag HandlersClassic tag handler is the designation used in the JSP 2.0 specification for the original version of the tag handler API, to distinguish it from the new, easier-to-use API. Given that a simple tag handler is so much easier to implement than a classic tag handler and can do exactly the same things, why bother with the classic tag handler at all? The answer is "you shouldn't," unless one of the following is true:
It is hard to do anything about the first reason; if the tag library must work with JSP versions prior to 2.0, you have no choice but to use classic tag handlers. Scripting code is rarely needed nowadays, so the second reason should be looked at with suspicion. I suggest that you think long and hard about other solutions, such as using JSTL, more custom actions, and EL expressions instead. The third reason is related to the fact that simple tag handlers cannot be reused; the container creates a new instance for every invocation. A container is, however, allowed to reuse classic tag handlers for multiple custom action invocations, as long as the strict rules (described later) are followed. While this may sound like a great performance boost, benchmarks have shown that it has very little effect in reality when using a modern Java version (J2SE 1.4 or later). In most cases, the cost of maintaining the pool eats up most of the gain from reduced object creation, since modern Java versions are pretty good at dealing with short-lived objects. Reuse can make a difference with an efficient pool for tag handlers that use resources that are very expensive to create but can be retained between multiple invocations, but these are very rare. A workaround is to maintain the resources as scoped variables instead, created by the first tag handler instance and reused by all others. That said, the classic tag handler is still supported in JSP 2.0, so I describe it in detail in this section. The main reasons for its complexity are due to the support for scripting elements in the custom action body and potentially reusable tag handler instances. The classic tag handler API contains three primary interfaces, all part of the javax.servlet.jsp.tagext package: Tag, IterationTag, and BodyTag. The Tag interface defines the methods you need to implement for any tag handler. The IterationTag interface extends the Tag interface and adds methods needed for iteration over the action element's body. The BodyTag interface extends the IterationTag interface and adds methods that provide access to the action element's body evaluation result. There's also a fourth interface named TryCatchFinally; it's a so-called mix-in interface, meaning it can be implemented in addition to any of the three main interfaces. It defines methods that let the tag handler deal with exceptions, for instance, exceptions thrown by JSP elements nested in the action element's body. To make it easier to develop a classic tag handler, two support classes are defined by the API: TagSupport and BodyTagSupport, as shown in Figure 21-3. The TagSupport class provides default implementations for the methods in both the Tag and the IterationTag interfaces, and BodyTagSupport adds defaults for the BodyTag interface methods. Figure 21-3. The primary classic tag handler interfaces and support classes![]() Declaration and deployment of classic tag handlers follow the same process as for simple tag handlers: declare the tag handlers in a TLD and make the TLD and class files available to the web application, either as files directly in the filesystem or packaged as a JAR file. The TLD elements are identical for both types, and you can mix simple and classic tag handlers in the same tag library. 21.2.1 Developing a Basic ActionThe main difference between the simple and classic tag handler APIs is that while the SimpleTag interface has only one method for asking the tag handler to complete its processing, the Tag interface has two: doStartTag( ) and doEndTag( ) (the Tag subinterfaces for more specialized classic tag handlers add even more methods). The container calls these methods when the start tag and end tag are encountered, as shown in Figure 21-4. Figure 21-4. Tag interface methods and property setter methods![]() Attribute values are set using bean setter methods, just as for the simple tag handler. The doStartTag( ) and doEndTag( ) method return values that controls what happens next, for instance how to deal with the custom action body. This is another significant difference compared to the simple tag handler; a classic tag handler needs to ask the container (through the return value) to evaluate the body rather than doing it itself. A tag handler that implements just the Tag interface can add dynamic content to the response body and set response headers, add or remove variables in one of the JSP scopes, and tell the container to either include the action element's body in the response or ignore it. Here are the most important methods of the Tag interface: public void setPageContext(PageContext pageContext); public int doStartTag( ) throws JspException; public int doEndTag( ) throws JspException; Let's first look at the implementations the TagSupport class provides for these methods. This is the class most simple tag handlers extend, so it's important to know how TagSupport implements the methods the tag handler inherits. The first method of interest is the setPageContext( ) method: public class TagSupport implements IterationTag, Serializable {
...
protected PageContext pageContext;
...
public void setPageContext(PageContext pageContext) {
this.pageContext = pageContext;
}
The JSP container calls this method before the tag handler is used. The TagSupport implementation simply sets an instance variable to the current PageContext object. As you may recall, the PageContext provides access to the request and response object and all the JSP scope variables, and it implements a number of utility methods the tag handler may use. When the start tag is encountered, the JSP container calls the doStartTag( ) method, implemented like this in the TagSupport class: public int doStartTag( ) throws JspException {
return SKIP_BODY;
}
This method gives the tag handler a chance to initialize itself, perhaps verifying that all attributes have valid values. Another use for this method is to decide what to do with the element's body content, if a body exists. The method returns an int that must be one of two values defined by the Tag interface: SKIP_BODY or EVAL_BODY_INCLUDE. The default implementation returns SKIP_BODY. As the name implies, this tells the JSP container to ignore the body completely. If EVAL_BODY_INCLUDE is returned, the JSP container evaluates the body (for instance, executes scripting elements and other actions in the body) and includes the result in the response. You can create a simple conditional tag—similar to the JSTL <c:if> action—by testing some condition (set by action attributes) in the doStartTag( ), and return either SKIP_BODY or EVAL_BODY_INCLUDE, depending on whether the condition is true or false. No matter which value the doStartTag( ) method returns, the JSP container calls doEndTag( ) when it encounters the end tag for the corresponding action element: public int doEndTag( ) throws JspException {
return EVAL_PAGE;
}
This is the method that most classic tag handlers override to do the real work. It can also return one of two int values defined by the Tag interface. The TagSupport class returns EVAL_PAGE, to tell the JSP container to continue processing the rest of the page. A tag handler can also return SKIP_PAGE, which aborts the processing of the rest of the page. This is appropriate for an action that forwards the processing to another page or sends a redirect response to the browser; the JSTL <c:redirect> action is one example. To get a better idea of how it all fits together, let's look at a classic tag implementation for the <ora:addCookie> action. You can compare it to the simple tag handler implementation for the same custom action described earlier to see how the two tag handler APIs differ. The tag handler class is called com.ora.jsp.tags.xmp.ClassicAddCookieTag and extends the TagSupport class to inherit most of the Tag interface method implementations: package com.ora.jsp.tags.xmp;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import com.ora.jsp.util.*;
public class ClassicAddCookieTag extends TagSupport {
private String name;
private String value;
private String maxAgeString;
public void setName(String name) {
this.name = name;
}
public void setValue(String value) {
this.value = value;
}
public void setMaxAge(String maxAgeString) {
this.maxAgeString = maxAgeString;
}
So far, the implementation is identical to the simple tag implementation, with setter methods for each attribute. The doEndTag( ) method looks like this: public int doEndTag( ) throws JspException {
int maxAge = -1;
if (maxAgeString != null) {
try {
maxAge = Integer.valueOf(maxAgeString).intValue( );
}
catch (NumberFormatException e) {
throw new JspException("Invalid maxAge: " +
e.getMessage( ));
}
}
CookieUtils.sendCookie(name, value, maxAge,
(HttpServletResponse) pageContext.getResponse( ));
return EVAL_PAGE;
}
Compared to the simple tag handler implementation there are two differences. First, the classic tag handler gets initialized with a PageContext instance instead of the more generic JspContext used for simple tag handlers; this happens in the setPageContext( ) method, as shown earlier. Hence, the response object can be retrieved directly from the context object without having to first cast it to the correct type. Second, the doEndTag( ) method has an int return type and must return one of EVAL_PAGE or SKIP_PAGE, as opposed to the void return type declared for the simple tag handler's doTag( ) method. The rest is the same as for the simple tag handler. The optional maxAge attribute is converted to an int, and the cookie is created and added to the response object by the sendCookie( ) method in the com.ora.jsp.util.CookieUtils class. A classic tag handler class should also implement the release( ) method, to release all references to objects that it has acquired: public void release( ) {
name = null;
value = null;
maxAgeString = null;
super.release( );
}
The container calls the release( ) method when the tag handler is no longer needed. The ClassicAddCookieTag class sets all its properties to null and calls super.release( ) to let the TagSupport class do the same. This makes all objects used by the tag handler available for garbage collection. 21.2.2 Developing an Iterating ActionFor a simple tag handler, iterative evaluation of the body is simply done within the doTag( ) method, as described earlier. A classic tag handler doesn't have this luxury. Instead, it must ask the container to evaluate the action element's body repeatedly until some condition is true. To do so, it implements the IterationTag interface, which contains only one method: public int doAfterBody( ) throws JspException, which is called by the container after it has processed the action element's body. A tag handler that implements the IterationTag interface is at first handled the same way as a tag handler implementing the Tag interface: the container calls all property setter methods and the doStartTag( ) method. Then things divert slightly, as illustrated in Figure 21-5. Figure 21-5. IterationTag interface methods![]() After the call to doStartTag( ), the doAfterBody( ) method may be called any number of times before the doEndTag( ) method is called. Let's implement the same scaled-down version of the <c:forEach> action that I used to illustrate iteration with a simple tag handler again but this time as a classic tag handler. As before, the scaled down version supports only Collection data structures. The class tag handler class is shown in Example 21-9. Example 21-9. A tag handler implementing the IterationTag interfacepackage com.ora.jsp.tags.xmp;
import java.util.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class ClassicSimpleLoopTag extends TagSupport {
private Iterator iterator;
private String items;
private String var;
public void setItems(String items) {
this.items = items;
}
public void setVar(String var) {
this.var = var;
}
public int doStartTag( ) throws JspException {
iterator = items.iterator( );
if (iterator.hasNext( )) {
pageContext.setAttribute(var, iterator.next( ));
return EVAL_BODY_INCLUDE;
}
else {
return SKIP_BODY;
}
}
public int doAfterBody( ) {
if (iterator.hasNext( )) {
pageContext.setAttribute(var, iterator.next( ));
return EVAL_BODY_AGAIN;
}
else {
return SKIP_BODY;
}
}
}
The ClassicSimpleLoopTag class extends TagSupport. The TagSupport class implements the IteratorTag interface and provides a default implementation for the method in this interface in addition to the methods in the Tag interface. The custom action has two mandatory attributes, each represented by a setter method in the tag handler class. The items attribute specifies an object that implements the Collection interface. The tag handler iterates over all collection elements and makes the current element available as a page-scope variable in the element's body. The var attribute specifies the name of the page-scope variable. The doStartTag( ) method first creates an Iterator for the collection. Note that the Iterator must be declared as an instance variable, since it's also used in the doAfterBody( ) method. If the Iterator contains at least one element, the doStartTag( ) method makes the first element in the Collection available as a page-scope object with the name specified by the var attribute and returns EVAL_BODY_INCLUDE. This tells the container to add the contents of the action element's body to the response and then call doAfterBody( ). The doAfterBody( ) method must return either EVAL_BODY_AGAIN (to iterate over the body) or SKIP_BODY (to stop the iteration). The TagSupport default implementation just returns SKIP_BODY. Except for not initializing the Iterator, the doAfterBody( ) method in the ClassicSimpleLoopTag class does exactly the same as the doStartTag( ) method. As long as the Iterator contains at least one more element, doAfterBody( ) returns EVAL_BODY_AGAIN. When all elements have been processed, it returns SKIP_BODY to stop the iteration. When the doAfterBody( ) method returns SKIP_BODY, the container calls the doEndTag( ) method. In this example, the default implementation provided by the TagSupport class is sufficient so there's no need to override it. It simply returns EVAL_PAGE to tell the container to process the rest of the page. 21.2.3 Processing the Action BodyIt's fairly easy to develop the most basic type of tag handlers even with the classic tag handler API. For a tag handler that needs to read and process the element body, the difference between the simple and the classic API is more apparent. A classic tag handler that needs access to the action element's body must implement the BodyTag interface and tell the container to capture the body evaluation result in an instance of the BodyContent class. The BodyTag interface extends the IterationTag interface and adds two new methods:
Figure 21-6 illustrates the container calling these new methods, which are relative to the methods inherited from the IterationTag interface. Figure 21-6. BodyTag interface methods![]() As with the Tag and IterationTag interfaces, there's a support class that implements all the methods of the BodyTag interface, plus a few utility methods: public class BodyTagSupport extends TagSupport implements BodyTag The BodyTagSupport class overrides the doStartTag( ) method inherited from the TagSupport class: public int doStartTag( ) throws JspException {
return EVAL_BODY_BUFFERED;
}
Instead of returning SKIP_BODY as the TagSupport class does, it returns EVAL_BODY_BUFFERED. The EVAL_BODY_BUFFERED value is only valid for a tag handler that implements the BodyTag interface. It means that not only should the action's body be evaluated, but the container must also buffer the result and make it available to the tag handler. The container uses a BodyContent object to buffer the body evaluation result—static text as well as dynamic content created by nested action and scripting elements. This is a subclass of the JspWriter, the class used to write text to the response body. In addition to the inherited methods for writing text, the BodyContent class has methods the tag handler can use to read the body evaluation result. This is how it works. To buffer the response body, as described in Chapter 17, the container creates an instance of the JspWriter class before processing the page and directs all output to this instance. Everything that's added to the response body—explicitly by JSP elements or implicitly by the JSP container (template text)—therefore ends up in the JspWriter first before it's sent to the browser. When the JSP container encounters a custom action with a tag handler that implements the BodyTag interface, it temporarily redirects all output to a BodyContent instance until it reaches the action's end tag. The content produced when the element body is processed is therefore buffered in the BodyContent instance where the tag handler can then read it. The container gives the tag handler a reference to the BodyContent instance by calling the setBodyContent( ) method: ...
protected BodyContent bodyContent;
...
public void setBodyContent(BodyContent b) {
this.bodyContent = b;
}
The BodyTagSupport class simply saves the reference to the BodyContent in an instance variable. Before the container evaluates the body, it gives the tag handler a chance to initialize itself by calling doInitBody( ): public void doInitBody( ) throws JspException {
}
The implementation in BodyTagSupport does nothing. A tag handler can override this method to prepare for the first pass through the action body, perhaps writing initial content to the BodyContent that should precede any content added by the evaluation of the nested elements. This method is, however, rarely used. When the body has been processed, the doAfterBody( ) method is invoked: public int doAfterBody( ) throws JspException {
return SKIP_BODY;
}
The same as with the IterationTag interface, this method gives the tag handler a chance to decide whether the body should be processed again. If so, it returns the EVAL_BODY_AGAIN value, otherwise SKIP_BODY. As opposed to a tag handler that implements only the IterationTag interface, a BodyTag implementation can also use this method to read the buffered body content and process it in some way. We'll look at an example of this shortly. The BodyTagSupport implementation returns SKIP_BODY to let the processing continue to the doEndTag( ) method. As with a tag handler implementing the Tag interface, this method must return either EVAL_PAGE or SKIP_PAGE. To see how it all comes together we implement the <ora:menuItem> custom action from Chapter 17 again, but as a classic tag handler this time. Example 21-10 shows the code for the tag handler class. Example 21-10. The ClassicMenuItemTag classpackage com.ora.jsp.tags.xmp;
import java.io.*;
import java.util.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import com.ora.jsp.util.StringFormat;
public class ClassicMenuItemTag extends BodyTagSupport {
private String page;
public void setPage(String page) {
this.page = page;
}
public int doEndTag( ) throws JspException {
HttpServletRequest request =
(HttpServletRequest) pageContext.getRequest( );
String requestURI = request.getServletPath( );
String pageURI = StringFormat.toContextRelativeURI(page,
requestURI);
StringBuffer text = null;
String body = getBodyContent().getString( );
if (requestURI.equals(pageURI)) {
text = new StringBuffer(body);
}
else {
String contextPath = request.getContextPath( );
String uri = contextPath + pageURI;
HttpServletResponse res =
(HttpServletResponse) pageContext.getResponse( );
text = new StringBuffer("<a href=\"");
text.append(res.encodeURL(uri)).append("\">").
append(body).append("</a>");
}
try {
JspWriter out = getPreviousOut( );
out.print(text);
}
catch (IOException e) {}
return EVAL_PAGE;
}
public void release( ) {
page = null;
super.release( );
}
}
The tag handler extends the BodyTagSupport class and overrides only the doEndTag( ) method. It also implements a setter method for the page attribute. Except for how the body evaluation result is captured and how text is added to the current response stream, the doEndTag( ) method looks identical to the doTag( ) method in the simple tag handler we developed earlier. The classic tag handler in Example 21-10 uses BodyTagSupport utility methods to handle both tasks. The getBodyContent( ) method returns a reference to the BodyContent object and its content is read by the getString( ) method. The BodyContent class also provides a getReader( ) method to get the content as a Reader, which can be handy if you need to process the content as a stream, perhaps with an XML parser. To get hold of an appropriate writer for the generated content, the tag handler calls the getPreviousOut( ) method. It returns the BodyContent of the enclosing action, if any, or the main JspWriter for the page if the action is at the top level. You may be wondering why the method is called getPreviousOut( ) as opposed to getOut( ). The name is intended to emphasize the fact that you want to use the object assigned as the output for the enclosing element in a hierarchy of nested action elements. Say you have the following action elements in a page: <xmp:foo>
<xmp:bar>
Some template text
</xmp:bar>
</xmp:foo>
Let's recap how buffering works. The JSP container first creates a JspWriter and directs all output to it. When it encounters the <xmp:foo> action, it creates a BodyContent object and temporarily redirects the output. It creates another BodyContent for the <xmp:bar> action and, again, redirects the output. The container keeps track of this hierarchy of output objects. Template text and output produced by JSP elements end up in the current output object. Each element can get access to its own BodyContent object by calling the getBodyContent( ) method and then reading the content. For the <xmp:bar> element, the content is the template text. After processing the content, it can write it to the <xmp:foo> body by getting the BodyContent for this element through the getPreviousOut( ) method. Finally, the <xmp:foo> element can process the content provided by the <xmp:bar> element and add it to the top-level output object; it gets the JspWriter object by calling the getPreviousOut( ) method. This method is implemented by the BodyTagSupport class like this: public JspWriter getPreviousOut( ) {
return bodyContent.getEnclosingWriter( );
}
By calling getPreviousOut( ), the tag handler in Example 21-10 gets the proper writer: either a parent action's BodyContent or the top-level JspWriter. It then writes either the plain body content or the dynamically generated HTML link to it. 21.2.3.1 Dealing with empty elementsOne thing to note about tag handlers: when they're implementing the BodyTag interface, the container doesn't call all methods if the action element doesn't have a body in the JSP page—in other words, when the action is represented by an empty element in the page. An action element is considered empty if it's:
Note that the element isn't considered empty if the body contains anything—even so-called whitespace characters (blank, tab, linefeed) or scripting elements. For an empty custom action element with a tag handler that implements the BodyTag interface, the container doesn't call the following methods: setBodyContent( ), doInitBody( ), or doAfterBody( ). This allows the container to generate more efficient code for an empty BodyTag element, since it avoids creating a BodyContent instance that will never be used.[1]
If you're not careful, this can cause a problem for an action that can be used both with and without a body. An example is an action that lets the page author specify input either as an attribute value or as the element body. A typical mistake in this case is to assume that the tag handler always has access to a BodyContent instance and thus use code like this (directly or indirectly by calling the getPreviousOut( ) method) to get hold of the writer in the doEndTag( ) method: JspWriter out = bodyContent.getEnlosingWriter( ); This code throws a NullPointerException if the custom action is used without a body, because the setBodyContent( ) method is never called, and the bodyContent variable is therefore null. To avoid this problem, you should always check for null with code like this instead: JspWriter out = null;
if (bodyContent != null) {
out = bodyContent.getEnclosingWriter( );
}
else {
out = pageContext.getOut( );
}
An alternative is to access the bodyContent variable only in methods that are called exclusively for an element with a body, in other words, the doInitBody( ) and doAfterBody( ) methods. Another thing to think about for an action that is supposed to work with or without a body is this: do not put any logic that should be executed even for an empty tag in the doInitBody( ) and doAfterBody( ) methods. Logic that's needed even for an empty tag must be implemented by the doStartTag( ) and doEndTag( ) methods. 21.2.4 Handling ExceptionsIn most cases, the default handling for an exception thrown by JSP elements is sufficient; the container forwards control to an error page where you can display a nice, user-friendly message. But for some types of tag handlers—for instance a tag handler that uses a pooled resource (such as a connection from a connection pool)—there must be a fail-safe way to handle exceptions thrown by nested elements, for instance to return the shared resource to the pool. If exceptions aren't handled correctly, the resource pool "leaks," and eventually the application runs out of resources and comes to an embarrassing halt. None of the three main classic tag handler interfaces include methods that are called in case of an exception in the element's body, but a separate interface lets you deal with possible exceptions. The TryCatchFinally interface is a so-called mix-in interface, which means that a tag handler can only implement it in addition to one of the three main tag handler interfaces. It has two methods: public void doCatch(Throwable t) throws Trowable public void doFinally( ) Example 21-11 shows the classic tag handler version for the action that writes the result of the evaluation of its body to a file. Example 21-11. A tag handler implementing TryCatchFinallypackage com.ora.jsp.tags.xmp;
import java.io.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class ClassicFileWriteTag extends BodyTagSupport
implements TryCatchFinally {
private String fileName;
private PrintWriter pw;
public void setFileName(String fileName) {
this.fileName = fileName;
}
public int doStartTag( ) throws JspException {
if (fileName != null && !"log".equals(fileName)) {
try{
pw = new PrintWriter(new FileWriter(fileName, true));
}
catch (IOException e) {
throw new JspException("Can not open file " + fileName +
" for writing", e);
}
}
return EVAL_BODY_BUFFERED;
}
public int doAfterBody( ) throws JspException {
String content = bodyContent.getString( );
if (fileName == null) {
System.out.println(content);
}
else if ("log".equals(fileName)) {
ServletContext application = pageContext.getServletContext( );
application.log(content);
}
else {
pw.print(bodyContent.getString( ));
}
return SKIP_BODY;
}
public void doCatch(Throwable t) throws Throwable {
ServletContext application = pageContext.getServletContext( );
application.log("Exception in body of " +
this.getClass().getName( ), t);
throw t;
}
public void doFinally( ) {
if (pw != null) {
pw.close( );
}
}
}
If a filename is specified, the doStartTag( ) method tries to open the file for writing by creating a PrintWriter for it. If this fails, for instance because of an invalid filename or access permission problems, the IOException is caught, and a JspException is thrown with a message about what went wrong. In the doAfterBody( ) method, the content of the BodyContent instance for the tag handler is written to the file. The most interesting parts of this example are the doCatch( ) and the doFinally( ) methods. The container calls the doCatch( ) method if elements nested in the body or any of the doStartTag( ), doEndTag( ), doInitBody( ), or doAfterBody( ) methods throw a Throwable—the mother of all exceptions. In this example, it's called if the file can't be opened in the doStartTag( ) method or if a nested element throws an exception. All this method does in this example is to log the problem and rethrow the Throwable to let the container deal with it in the standard way. You don't have to rethrow the Throwable passed as an argument; in some cases it makes sense to throw another type of exception or no exception at all (to allow the processing of the rest of the page to continue). The doFinally( ) method is always called by the container—after doEndTag( ) in case of normal execution or after doCatch( ) in the exception case. In this example, the method simply closes the PrintWriter. By doing this in the doFinally( ) method, you're guaranteed that the file is always closed, ensuring that the application doesn't run out of file descriptors. One other thing to note here: I test if the pw variable is null before I close it. That's because this method is called even in the case where the doStartTag( ) method throws an exception because it can't create the PrintWriter. If I didn't check for null, the doFinally( ) method would throw a NullPointerException, hiding the real problem. 21.2.5 The Classic Tag Handler Lifecycle and What It Means to YouCreating a new object is considered a relatively expensive operation in Java, even though it's less of an issue with the latest Java runtime environments. For high-performance applications, it's therefore common to try to minimize the number of objects created and reuse existing objects instead. The lifecycle defined for classic tag handlers in JSP 1.2 allowed a tag handler instance to be reused within the code generated for JSP pages under certain circumstances. This feature has caused a lot of pain and misunderstanding, which is why the simple tag handlers introduced in JSP 2.0 cannot be reused at all; the potential small loss of performance is a huge gain in simplicity, leading to less error prone code. For backward compatibility and for the scenarios where reuse still makes a difference, classic tag handlers are still reusable in JSP 2.0. The classic tag handler lifecycle details are pretty complex and are mostly of interest to container developers. But if you develop classic tag handlers, you need to know at least how the lifecycle relates to instance reuse to ensure that your tag handlers work correctly in a container that takes advantage of this feature. Figure 21-7 shows a state diagram for a tag handler that implements just the Tag interface. Figure 21-7. Lifecycle for a tag handler implementing the Tag interface![]() When the tag handler instance is created, all instance variables have default values; then all setter methods (setPageContext( ), setParent( ), and all setters for attributes) are called. This brings the instance to a state where it's initialized for use. The doStartTag( ) method is then called. This method may set instance variables to values that are valid only for the current invocation. The doEndTag( ) method is called if no exception is thrown by doStartTag( ) or while processing the element's body. The tag handler instance may then be reused for another occurrence of the custom action that uses the same set of attributes, with the same or different values, in the same or a different page. If an attribute for the other occurrence has a different value, the corresponding setter method is called, followed by the doStartTag( )/doEndTag( ) called as before. Eventually, the container is ready to get rid of the tag handler instance. At this point, it calls the release( ) method to let the tag handler release internal resources it may have used. Let's look at what this means from a tag handler developer's perspective. There are a number of things you may need to do in your tag handler, for instance:
The following sections describe the requirements the tag handler lifecycle places on you to get this right. 21.2.5.1 Providing default values for optional attributesIf some attributes are optional, you must provide default values for the attributes. You can do so in a number of ways—for instance, in the variable declaration or through a getter method used by other tag handler methods: private int optionalInt = 5;
private java.util.Date optionalDate;
private java.util.Date getOptionalDate( ) {
if (optionalDate == null) {
return new java.utl.Date( );
}
else {
return optionalDate;
}
}
Given that the tag handler instance may be reused for another occurrence of the custom action, you may think you need to reset the attributes to their defaults before this happens. But that isn't the case. Look at the description of the lifecycle again. A tag handler instance can be reused only for an occurrence with the same set of attributes. Put another way, if a tag handler instance is used for an occurrence that doesn't use an optional attribute, it can be reused only for other occurrences that also omit this attribute. The default value will never need to be reset; it's never set for any of the occurrences that use the instance in the first place. Let's look at an example: <xmp:myAction attr1="one" /> <xmp:myAction attr1="one" attr2="two" /> <xmp:myAction attr1="one" attr2="new" /> Here the container creates one tag handler instance for the first action element and calls the setter method for attr1. This tag handler uses its default value for the optional attr2 attribute. The container isn't allowed to use the same tag handler instance for the other two action elements because they don't use the same set of attributes as the first element. Instead, it must create a new tag handler instance and call the setter methods for both attributes with the values specified by the second element. After using the tag handler for the second element, the container can reuse it for the third element. Only the setter method for attr2 must be called, because the value for attr1 is the same in the second and third elements. 21.2.5.2 Resetting per-invocation stateA tag handler may create or collect data that is only valid for one invocation. One example is a list of values set by custom actions nested in the body of the main action, for instance JSTL <c:param> actions adding values used by the <c:redirect> actions: <c:redirect url="mypage.jsp"> <c:param name="foo" value="bar" /> <c:param name="fee" value="baz" /> </c:forward> In this example, the nested parameter actions call a method in the tag handler for the parent action to add the parameter to a list that is then used in the forward URI: private Map params;
...
public void addParameter(String name, String value) {
if (params == null) {
params = new HashMap( );
}
params.put(name, value);
}
If the container decides to reuse this tag handler, the list grows for each invocation unless you reset it at some point. There's no guarantee that the doEndTag( ) method is called (in case of an exception in the body), so the best place to reset the list is in the doStartTag( ) method: public int doStartTag( ) throws JspException {
// Reset per-invocation state
params = null;
...
}
This approach works fine for objects that can hang around until the tag handler is used again. But what if you need to use an expensive resource, such as a database connection, that must be released (or returned to a pool) as soon as possible? That's when the TryCatchFinally interface comes in handy. As I described earlier and showed in Example 21-11, the doFinally( ) method is always called, no matter if an exception is thrown or not. Expensive resources that are used only on a per-invocation basis can be released in this method. 21.2.5.3 Keeping expensive resources for the lifetime of the tag handler instanceSome objects used by a tag handler can be expensive to create, such as a java.text.SimpleDateFormat instance or an XML parser. Instead of creating objects like this every time the tag handler is invoked, it's better to create them once when the tag handler itself is created or the first time they are used. The place to get rid of objects like this is in the release( ) method: private java.text.SimpleDateFormat dateFormat =
new java.text.SimpleDateFormat( );
...
public void release( ) {
dateFormat = null;
}
The release( ) method is called just before the container gets rid of the tag handler to let it do this kind of cleanup. It's never called between invocations. |
| [ Team LiB ] |
|