Our tour of Java Server Pages wraps up with a discussion of custom taglibs. The example code for this post can be found in the jsp_taglib
branch of our example repository on Github, and a ZIP file of the code can be found here.
So far, we’ve only looked at how to directly inline Java code into our JSP pages. This approach does its job, but it doesn’t have a lot of fans among Java developers, for reasons we’ve discussed before (code gets messy, business logic gets stuffed into the presentation layer, etc). This is where custom taglibs come into play as an alternative.
JSP allows developers to write their own custom HTML tags which are backed by Java code. A taglib is simply a collection of one or more of these tags, and you can create as many of them as you need.
How to write and configure a custom taglib
The basic process for creating a custom taglib looks something like this:
- Write a class which implements a special Tag interfaces.
- Write a TLD file which maps each tag name to its corresponding Java class.
- Include the tag library in your JSP page, and use your tag(s) as needed.
Classic and Simple Tags
JSP 2.0 introduced an alternate tag interface that users could implement. Called SimpleTag
, it was meant to be simpler in terms of implementation, but just as powerful in features.
This means there are two different ways to write a tag - the old way (now called “classic tags”), and via SimpleTag
.
I’m not going to lie - this post took me a very long time to write because of anxiety I had about writing about the two kinds of tags. Generally speaking, they have the same features, but technically speaking, there are minor differences in how they work, and a ton of differences in how they’re written. I realized that I would drive myself crazy covering all the minutiae, so for the sake of my sanity I’m going to stick with the modern simple tag approach.
Writing a Simple Tag
Before we write some code, we need to add a new dependency to our build. So far, we’ve been able to get away with relying on nothing more than the Servlet API, since our web server contains all the resources needed to handle JSPs. But not we’re actually going to be writing against the JSP API, so we have to add a dependency to it in build.gradle:
providedCompile 'javax.servlet:jsp-api:2.0'
Now to begin.
Simple Tags start off by implementing the SimpleTag
interface. For the sake of convenience, the JSP spec provides a class, SimpleTagSupport
, which already implements it, and provides defaults for every method. This leaves us with much less code to write.
In fact, we really only need to override one method, called doTag()
. From here, we can do most of what we want to achieve.
Here’s an example of an incredibly basic tag:
package net.cmwolfe;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
/**
* A <i>very</i> basic example of how to write a custom JSP tag.
*/
public class FirstSimpleTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
JspWriter out = getJspContext().getOut();
out.println("We can write text out to the body of the custom tag");
}
}
Not too much to observe here. We can use the call to getJspContext()
to access to the JSP context object, through which we can gain access to the PrintWriter object of the JSP page (remember, JSP’s are Servlets, and each Servlet has a PrintWriter for writing output). We use the PrintWriter to output a simple String.
Before we examine what this tag will do, we have to look at two more files. The first is the Tag Library Descriptor, or TLD file. The TLD file defines which tags are in our custom taglib, and which Java classes define each tag. TLD files should be put in the WEB-INF/
folder of your webapp. Here’s ours, named customtags.tld:
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>2.0</jsp-version>
<short-name>Simple Custom Tag</short-name>
<tag>
<name>simple_tag</name>
<tag-class>net.cmwolfe.FirstSimpleTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
We define one tag, named simple_tag
, and map it to the class we wrote. The body-content
section defines what kind of content is allowed in the body of tag. In this case, we allow nothing.
Lastly, we should take a glance at the JSP page in which we will use our tag. I named it firstsimpletag.jsp
:
<%@ taglib prefix="cmw" uri="WEB-INF/customtags.tld"%>
<html>
<head>
<title>First Simple Tag</title>
</head>
<body>
<cmw:simple_tag/>
</body>
</html>
Notice the taglib
tag near the top of the file. It defines a prefix, cmw
, which we will use to prefix the name of any tags from the taglib. This way, if we were to use another taglib with similarly named tags, they won’t get mixed up. We also specify the URI to the TLD file we wrote. This allows the taglib to be imported for use.
The actual tag is used on the line containing <cmw:simple_tag/>
. cmw
comes from the prefix we defined above, and simple_tag
is the tag name we specified in the TLD.
Now, let’s finally take a look at how this JSP page is rendered in the browser:
Looks like the text we output is displayed on the page. Not exciting, I know, but this was a basic example, remember?
Okay, fine, we’ll do something else. Let’s write a custom tag that allows us to insert some text between the tag, and then do something with it.
We’ll create a new class, TagWithBody
, that looks like this:
package net.cmwolfe;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.io.StringWriter;
/**
* Example of a JSP custom tag that manipulates its body content.
*/
public class TagWithBody extends SimpleTagSupport {
@Override
public void doTag() throws JspException, IOException {
StringWriter writer = new StringWriter();
// Here we retrieve the tag body so we can use it.
getJspBody().invoke(writer);
getJspContext().getOut().write(writer.toString().toUpperCase());
}
}
In this class, we use a method called getJspBody()
to get the contents of the tag. We then convert those contents to uppercase, and write them back out.
We now have to add another definition to our TLD file, like so:
<tag>
<name>simple_tag</name>
<tag-class>net.cmwolfe.TagWithBody</tag-class>
<body-content>scriptless</body-content>
</tag>
Notice that the body-content
section has changed to scriptless
. This means that the tag can contain regular text and HTML, but no JSP tags (if instead you set it to JSP
, you could indeed embed JSP statements and scriptless within your custom tag).
Lastly, the JSP page in which we’ll use our tag, named tagwithbody.jsp
:
<%@ taglib prefix="cmw" uri="WEB-INF/customtags.tld"%>
<html>
<head>
<title>Tag With Body</title>
</head>
<body>
<cmw:simple_tag/>
<br>
<cmw:tag_with_body>
this is a tag with a body
</cmw:tag_with_body>
</body>
</html>
We have some text sandwiched between the start and close tags. Let’s see how it gets rendered in the output:
The text is there, but now it is in uppercase. This makes sense considering the code we wrote in the Java class, but there’s one important observation to make - the original, lowercase text is not displayed. And if you were to comment out all the code in the class, and reload the application, it still wouldn’t be displayed.
This tells us that when a JSP page is compiled, a custom tag is replaced with the code we wrote for that particular tag. And if that code produces no output, nothing will be rendered where the tag once stood. This is going to be an important point later on in this post.
There’s one final example I want to demonstrate; the use of tag attributes. To use an attribute, we add it as a field in our class, complete with a setter method. Here’s TagWithAttribute.java
:
package net.cmwolfe;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
/**
* Example of a JSP custom tag with a tag attribute.
*/
public class TagWithAttribute extends SimpleTagSupport {
// The name of the field is the attribute name we'll use in the JSP page.
private String prop;
public void setProp(String prop) {
this.prop= prop;
}
@Override
public void doTag() throws JspException, IOException {
boolean isEmpty = prop == null || prop.isEmpty();
getJspContext().getOut().write("Is our attribute empty? " + (isEmpty ? "Yes" : "No"));
}
}
We define a String property named prop
, and give it a setter. In the doTag()
method, we check to see whether the property was actually set when the tag was used, and output the result of this check. Here’s how we add this tag to the TLD file:
<tag>
<name>tag_with_attribute</name>
<tag-class>net.cmwolfe.TagWithAttribute</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>prop</name>
</attribute>
</tag>
Notice that we added a new block which specifies that our tag can accept an attribute. Lastly, here is tagwithattribute.jsp
:
<%@ taglib prefix="cmw" uri="WEB-INF/customtags.tld"%>
<html>
<head>
<title>Tag With Attribute</title>
</head>
<body>
<cmw:tag_with_attribute prop="property"/>
</body>
</html>
We added the prop
attribute to the custom tag; if we were to open the page in the web browser, the output would say “Is out attribute empty? No”. If we were to remove the attribute and redeploy, the answer would switch to “Yes”.
General Comments
I’ve read a lot of articles which encourage the use of custom tags in favor of raw JSP tags. Here’s one such example. These pieces typically emphasize the fact that taglibs allow you to enforce separation of concerns, by keeping your business logic out of your presentation code. I agree with this advice, but it is only half the story.
When we talk about business logic, we’re talking about code that determines what data gets retrieved, how it is processed, etc. Certainly, we should keep something like, say, database code out of a JSP page, and a custom tag can help us with that.
But what about presentation logic? That is to say, what if we’re talking about code that determines what gets rendered on the page? I’m talking about code that uses a loop to repeat a line multiple times, or code that renders a different line of markup based on the contents of a variable. Does this kind of code have to be hidden behind a custom tag? I assert that it does not. Sure, if I had some presentation logic that I planned on using on more than one page, I’d put it in a tag so I only had to write it once. But if we’re talking about a small, one off thing used on a single page, it might be easier to just stick it in a Scriptlet.
Proponents of taglibs would argue against my assertion, by claiming that doing so would expose our web designers to Java code that they won’t understand. This argument doesn’t make any sense to me. If we replaced inline JSP code with a custom tag, and said nothing about it to our hypothetical web developer, the first thing they would do is freak out when they look at the rendered page and see markup that wasn’t there in the HTML file. Then they would come to us to ask what’s going on, at which point we will either have to a) provide easy to understand documentation on how to use the tag, or b) show them the Java code anyway. At the end of the day, if we choose to use JSP to control the content on our page, then we’ve already made a conscientious choice to mix concerns, or at least programming languages. If you don’t want your web guys to be exposed to Java, you probably shouldn’t be using JSP in the first place.
The point of all this is not to discourage the use of custom taglibs. Rather it is to discourage this notion that JSP pages should never contain any raw Java code. Yes, you shouldn’t be writing whole classes in your JSP file, but a loop here and an if
statement there is not the end of the world.
So please, use custom tags. Just make sure you use them for the right reasons, and not because someone told you you had to use them.
Extra Links
- It turns out that we did not cover all the possible features and options of the TLD file. This page from Oracle explains these features in detail, in case you’re curious.
Up Next
Now that we know how to write our own tags, we’ll take a look at the JSTL, an official taglib that provides tags to perform numerous common tasks.