JSP and Java beans provide a nice way to abstract data access away from the page itself, but it still doesn’t get code entirely out of the system. Page designers still must use script elements for control functions such as loops or branching logic. In addition, Java beans sometimes aren’t enough to fully encapsulate a web application’s internal logic, leaving large chunks of code embedded in JSPs. And of course, there’s the matter of code reuse. While utility methods can be abstracted out into classes and made available to JSPs, scripting code embedded within a particular JSP tends not to lend itself to abstraction.
The JSP specification addresses this by allowing the creation of custom tags. Tags are organized into tag libraries , which can be loaded by pages as needed. Each tag library gets its own namespace and can be shared by as many pages as necessary. Properly designed custom tags can cut most, if not all, of the remaining pure Java code out your JSPs. We’ve already seen tags in action, since JSTL is just a collection of JSP tags that are a standard part of the environment.
Imagine a simple tag that displays the current date and time.
Let’s call the tag Date
. After
creating and configuring it, we can access it within a JSP
page:
<enterprise:Date/>
Note that the namespace is no longer jsp
. Instead, we have a custom specified
namespace that identifies a tag library, or
taglib, containing a set of custom tags. We
declare the tag library at the beginning of the JSP file with the
<%@ taglib %>
directive:
<%@ taglib uri="http://oreilly.com/enterprise/examples-taglib" prefix="enterprise"%>
The taglib containing the Date
tag is identified by a specific URI.
First, the server looks in the web.xml file for a
mapping from the URI to a taglib file in the web archive. If there is
not a mapping, then the server tries to find the taglib using the URI
as a direct path. A taglib mapping is made in the
web.xml file using a <taglib>
element, like so:
<taglib> <taglib-uri>http://oreilly.com/enterprise/examples-taglib</taglib-uri> <taglib-location> /WEB-INF/jsp/enterprise-taglib.tld </taglib-location> </taglib>
The <taglib-location>
tag points to a .tld file, which is an
XML-formatted tag library deployment descriptor. Here’s the
.tld file that describes a single tag
library:
<?xml version="1.0" encoding="ISO-8859-1"?> <taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"> <description>A simple tag library for JEIAN</description> <tlib-version>1.0</tlib-version> <short-name>enterprise</short-name> <uri>http://oreilly.com/enterprise/examples-taglib</uri> <tag> <description>Display current date</description> <name>Date</name> <tag-class>com.oreilly.jent.jsp.DateTag</tag-class> <body-content>empty</body-content> </tag> </taglib>
The key thing here is the <tag>
tag with its subtags, <name>
and <tag-class>
(descriptions are
optional). The <name>
tag
specifies the name that JSPs will use to refer to the tag itself, and
the <tag-class>
specifies the
Java class that implements the tag functionality. In this case, it’s a
class named DateTag
in the enterprise
package. This class file must be
available to the web application.
By this point, we’ve described a tag, put it in a library,
associated the library with the web application, retrieved the library
from the web application for use in a JSP page, mapped it to the
enterprise
namespace for the page,
and invoked the tag itself. The only thing remaining is the actual
implementing class.
Custom tags come in two basic varieties: basic tags and body
tags. Basic tags don’t access their bodies (the content between the
open and close tags), although they can control whether or not the
JSP servlet processes their content. Body tags are just like basic
tags, but have access to their body content. Support interfaces and
classes for all custom tags are in the javax.servlet.jsp.tagext
package.
Basic tags implement the Tag
interface, which has two methods of
interest to the tag developer: doStartTag()
and doEndTag()
. The doStartTag()
method is called when the
page processing reaches the opening of the tag (<enterprise:Date>
in the previous
example), and the doEndTag()
method is called when execution reaches the end tag (</enterprise:Date>
). These are
called sequentially for empty tags.
The doStartTag()
method can
return Tag.SKIP_BODY
or Tag.EVAL_BODY_INCLUDE
. Skipping the body
causes execution to jump right to the end of the tag. Evaluating the
body will cause the JSP to process the body normally, as if it were
outside of the tag. The doEndTag()
method can return Tag.EVAL_PAGE
or Tag.SKIP_PAGE
. If EVAL_PAGE
is returned, the rest of the
page will be processed; if SKIP_PAGE
is returned, processing
stops.
Here, finally, is the DateTag
class. It extends the TagSupport
class, which implements the
Tag
interface and provides stub
versions of both do-
methods, as
well as the other methods in the interface (which are used by the
JSP to provide resources to the tag). It implements the doEndTag()
method:
import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class DateTag extends TagSupport { public int doEndTag() throws JspException { try { pageContext.getOut().println((new java.util.Date()).toString()); } catch (java.io.IOException e) { throw new JspException(e); } return Tag.EVAL_PAGE; } }
The pageContext
object is
an instance of PageContext
and is
provided by the TagSupport
class.
The PageContext
object provides
access to the current output writer (via getOut()
), as well as the ServletRequest
and ServletResponse
objects for the request,
page attributes, ServletConfig
and ServletContext
objects, and
the HttpSession
object.
Shown below is another TagSupport
-based tag, which is a little
fancier. The <enterprise:Evening>
tag encloses
content and displays it only if the current server time is not
within the workday. Here’s how it works in a JSP:
<enterprise:Evening dayStart="9" dayEnd="17"> Sorry, we're closed for the day. Please try us tomorrow. </enterprise:Evening>
The code is simple, but this time we extend the doStartTag()
method instead and return
SKIP_BODY
if the current time is
within the working day:
import java.util.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class EveningTag extends TagSupport { private int dayStart = 9; private int dayEnd = 17; // 24 hour time // In production, we should propagate errors with exceptions public void setDayStart(String value) { dayStart = Integer.parseInt(value); } public void setDayEnd(String value) { dayEnd = Integer.parseInt(value); } public int doStartTag() throws JspException { int hour = GregorianCalendar.getInstance().get(Calendar.HOUR_OF_DAY); if((hour >= dayStart) && (hour <= dayEnd)) return Tag.SKIP_BODY; return Tag.EVAL_BODY_INCLUDE; } }
Note that we also have two optional tag attributes: dayStart
and dayEnd
, which accept an hour value,
defaulting to 9 A.M. to 5 P.M. These values are communicated to the
Tag
via JavaBean-style setter
objects. The JSP implementation will call the setters for every
supplied attribute before calling any of the do
XXX
()
methods. Attributes also need to be
declared in the tag library descriptor. Here’s the extension to the
previous tag library for the Evening
tag:
<tag> <description>Display text if it's the evening</description> <name>Evening</name> <tag-class>com.oreilly.jent.jsp.EveningTag</tag-class> <body-content>JSP</body-content> <attribute> <name>dayStart</name> <required>false</required> </attribute> <attribute> <name>dayEnd</name> <required>false</required> </attribute> </tag>
JSP tags are frequently called upon to perform an action more
than once. The JSP specification supports this capability via the
IterationTag
interface, which
adds an additional lifecycle method, doAfterBody()
. The doAfterBody()
method is invoked after the
tag body has been processed and can return IterationTag.EVAL_BODY_AGAIN
to have the
tag body reprocessed. The tag body is reprocessed immediately, and
the doAfterBody()
method is
called again. Returning Tag.SKIP_BODY
stops the loop.
The next example creates a tag named <enterprise:Loop>
, which processes
its content a given number of times (specified via the times
attribute).
import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class ForTag extends TagSupport { private int current = 1; private int times = 0; public void setTimes(int i) { times = i; } public int doStartTag() throws JspException { pageContext.setAttribute("current", new Integer(current)); if(current < times) return Tag.EVAL_BODY_INCLUDE; returnTag.SKIP_BODY; } public int doAfterBody() throws JspException { if(current >= times) return Tag.SKIP_BODY; current ++; pageContext.setAttribute("current", new Integer(current)); return IterationTag.EVAL_BODY_AGAIN; } }
This tag also uses the setAttribute()
method to create a variable
in the page context named current
. This allows us to see the loop
count in our JSP body. Here’s an example:
<enterprise:Loop times="4"> Loop <%= current %> <br> </enterprise:Loop>
The output in the browser will be:
Loop 1 Loop 2 Loop 3 Loop 4
We can’t just create variables at will, though. At the
minimum, they have to be described in the tag library descriptor, so
that the JSP compiler can create the appropriate references. Here’s
the descriptor for the Loop
tag:
<tag> <name>Loop</name> <tag-class>com.oreilly.jent.jsp.ForTag</tag-class> <description>For Loop</description> <variable> <name-given>current</name-given> <variable-class>java.lang.Integer</variable-class> <scope>NESTED</scope> </variable> <attribute> <name>times</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
The <variable>
tag
declares the current
variable as
a java.lang.Integer
. Its scope is
defined as NESTED
, which means it
will be available to code inside the tag only. The other legal
values are AT_BEGIN
and AT_END
. If you want to create variables
dynamically (for instance, to allow the user to choose the name of
the counter variable), you can create a TagExtraInfo
class and associate it with
the tag using <tei-class>
.
Also, note the <rtexprvalue>
tag associated with
the times
attribute. This means
that the attribute can accept a JSP expression as its value. So if
you have created a variable named count
, the Loop
tag could be opened with:
<enterprise:Loop times="<%= count %>">
Tags that need to interact with their body content
extend the BodyTag
interface
instead of the Tag
interface.
BodyTag
introduces a new method,
doInitBody()
, which runs before
the tag body is first processed. It also introduces the BodyContent
object, which provides access
to the tag body. BodyContent
is
an extension of JspWriter
, with
additional methods to retrieve its content in various ways,
including as a String
object. The
BodyTagSupport
class fills the
same role for BodyTag
that
TagSupport
does for Tag and
IterationTag
. All BodyTag
objects are also IterationTag
objects.
The next example shows a simple tag that retrieves the body
content, processes it through the java.net.URLEncoder
object, and outputs
the result:
import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; public class EncodeTag extends BodyTagSupport { public int doAfterBody() throws JspException { BodyContent c = getBodyContent(); // Get the tag body JspWriter out = getPreviousOut(); // Get the enclosing writer try { out.print(java.net.URLEncoder.encode(c.getString())); } catch(java.io.IOException e) { throw new JspException(e); } return BodyTag.SKIP_BODY; // Do not process again } }
The getPreviousOut()
method
of BodyTagSupport
retrieves the
JspWriter
associated with the
tag’s parent. This might be the main JspWriter
for the page, or it might be the
BodyContent
object of another JSP
tag.
As just mentioned, it’s possible for JSP custom tags
to be nested within other JSP custom tags. The inner tags are
executed between the doStartTag()
and doEndTag()
methods of the
outer tags. Let’s imagine an <enterprise:Email>
tag. In the JSP
file, it might be used like this:
<enterprise:Email> <enterprise:EmailAddress addr="from">deb@company.com </enterprise:EmailAddress> <enterprise:EmailAddress addr="to">will@company.com </enterprise:EmailAddress> <enterprise:EmailText>Where's that JSP Chapter?</enterprise:EmailText> </enterprise:Email>
The Email
tag is
implemented with either a Tag
or
a BodyTag
, with JavaBean-style
methods to set from and to addresses and email content. The doEndTag()
method assembles and sends the
email. Assuming that the doStartTag()
method of the email tag class
doesn’t stop the tag’s processing, each of the three interior tags
will be processed in turn. These tags can access the parent tag
using the findAncestorWithClass()
method, which takes a Tag
and a
class
reference and finds the
first appearance of the specified class in the tag’s hierarchy,
working up from the specified tag.
Here’s the code for the EmailAddress
tag, which sets the to and
from addresses of the EmailTag
class (not included):
import javax.servlet.jsp.tagext.*; public class EmailAddressTag extends BodyTagSupport { String addrType = null; public void setAddr(String s) { addrType = s; } public int doEndTag() { Tag t = findAncestorWithClass(this, enterprise.EmailTag.class); if(t != null) { EmailTag et = (EmailTag)t; if(addrType.equalsIgnoreCase("to")) et.setToAddress(getBodyContent().getString()); else if (addrType.equalsIgnoreCase("from")) et.setFromAddress(getBodyContent().getString()); } return Tag.EVAL_PAGE; } }
And that’s all there is to it. If the EmailAddressTag
object doesn’t find an
EmailTag
in its parent hierarchy,
it exits without taking any action.
Get Java Enterprise in a Nutshell, Third Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.