Custom JSP Actions
Once we have written our class, we can write a TLD that describes it to the JSP engine. Many people might prefer to work in the opposite direction, using the TLD as a specification JSP authors and tag handlers can use while working in parallel. I prefer to write the custom actions first, modifying the TLD as I go along, even though this is admittedly not the safest nor the most elegant means of working.
The TLD, as you can see from Listing 2, can be a relatively short XML file. The TLD maps action names to the classes that implement those actions. A TLD can map a single action to a single class, or it might map hundreds of actions to hundreds of different classes. And because each class exists separately, it is even possible (though hardly a good idea) for a class to be used in multiple TLDs simultaneously.
The TLD is loaded into our servlet container when it is first referenced. Unfortunately, this means that changing the TLD after the custom action has already been invoked requires restarting Tomcat (and Apache, if you are using Apache's mod_jk along with the Tomcat server). It tells the JSP engine which versions and specifications your tag library supports, making it possible for a JSP engine to know when a particular library needs to be upgraded in order to be compatible with current standards.
The TLD consists of a top-level <taglib> tag, which contains a minimum of four sections: <tlibversion> indicates the version of the tag library specification this library supports; <jspversion> indicates the version of the JSP specification for which the tag library was written; <shortname> gives this tag library a name, which some JSP engines use; and <tag> appears once for every tag handler class we want to include in our library. Each tag gets its own name, the name of the action that is invoked. Thus, if we import a tag library with a prefix of “abc”, the tag named “hello” will be invoked as “abc:hello”. The <tagclass> section maps the tag's name to the tag handler class that actually performs the actions; this class must obviously be in your server's CLASSPATH. The <info> section allows us to provide some basic information and in-line documentation about this particular tag.
Finally, we name each of the attributes this custom action takes. Each attribute has its own <name> tag, as well as an indication of whether the attribute is required.
Now that we have a TLD and a tag handler class, we can use them together in any of our JSPs. We import the tag library using the special JSP taglib directive:
<%@ taglib uri="/WEB-INF/hello.tld" prefix="hello" %>
Notice how the taglib directive takes two parameters, “uri” and “prefix”. The uri portion contains the filename of the TLD that we just created. If you want to put TLDs directly inside your WEB-INF directory, then the above syntax is perfectly valid. The prefix parameter is a sort of namespace declaration, telling the JSP engine what prefix we will attach to each of the actions the tag library imports. Giving the JSP the option of naming the prefix, rather than building it into the tag library itself, allows us to import multiple tag libraries without having to worry about namespace clashes.
Since our TLD defines a single “hello” tag, and since we imported the tag library using the “hello” prefix, we can invoke our HelloTag methods using the following syntax: <hello:hello/>. Listing 3 contains a complete JSP (test-tag.jsp) that demonstrates how we can use this tag.
Remember to include the trailing slash when invoking custom actions. If you forget to include it, Tomcat's JSP engine (known as Jasper) will produce an error message similar to the following:
Unterminated user-defined tag: ending tag </hello:hello> not found or incorrectly nested
Our TLD indicates the firstname attribute is optional. If we don't pass a firstname parameter, then we get the following output in our web browser:
This is a test of our custom action. Hi there!We can also pass an optional firstname parameter:
<hello:hello firstname="Reuven"/>If we put the above in our JSP, the following output is sent to the browser:
This is a test of our custom action. Hello, Reuven