Tag Libraries: JSPs in the Enterprise
This article discusses Java Server Pages (JSPs) in the enterprise, which provide the presentation tier of a Web application. I walk through some of the problems developers face when working with JSPs and offer some practical solutions. As we abstract the code, we will come upon Tag Libraries as a great solution, especially for large enterprise systems. All of the code presented should work under any JSP 1.1-compliant server (Apache Tomcat/BEA WebLogic/Borland App Server, etc.).
All of the examples will revolve around calculating a number in the Fibonnaci Sequence, which is a mathematical sequence. The name comes from Leonardo Fibonacci, a biologist who stumbled on the sequence when monitoring rabbits reproducing. The Fibonnaci Sequence is a simple example and is used to focus on the real issues at hand. In the real world, the business logic you will be solving will most likely be more complicated than solving an equation, and will potentially use resources such as databases, naming services, EJB, etc.
The first step in embedding business logic into a JSP is to develop a JSP that calculates the Fibonnaci Sequence for the first 10 periods. You can jump right in and put the calculation into a method, which is then placed in a JSP declaration. A JSP declaration is delimited by the tags <%! and %>.
A scriptlet (<% ..%>) puts the code in the jspService() method, which is run every time a request comes in from a browser. It has access to the request and response objects, and is tied to the incoming client. Code inside a declaration is placed at the class level of the generated servlet. This allows you to declare methods and variables within a class. You cannot declare a method within a scriptlet, as Java does not allow you to declare a method within another one. The code in Listing 1 calculates the Fibonnaci Sequence for the numbers zero to nine. The top of the file declares the calculateFibonnaci(int x) method that is responsible for doing the calculation. Later in the JSP, we have a scriptlet with a for() loop going from zero to nine, calling calculateFibonnaci() on each number.
Using JavaBeans Via the Action
We have a working JSP in Listing 1. Imagine if you had another page on your site that wanted the same functionality. It would be a poor design to use the "copy and paste" pattern, putting the same declaration in every page that needed to calculate the Fibonnaci equation. Fortunately, the JSP framework has a standard way of working with stand-alone JavaBeans. There is a special set of actions to work with these classes. So, using a JavaBean we get:
" attribute gives a name to the bean you want to use. This name is a reference that can be used to talk to the bean later in the page (e.g., call a method on it). The "class
" attribute defines the fully qualified class of the bean, and the "scope
" attribute tells the JSP engine where to look for, or put, the bean (see Table 1).
|Table 1. JavaBean scope descriptions.
||Accessible only from within the current JSP.
||Accessible only from within the current request, i.e., this page and all resources to which the page is forwarded, via the ServletRequest object.
||Accessible to all pages within the current session via the HttpSession object.
||Accessible to all pages executing in the current application, i.e., Servlet/JSP code within the current Web server, via the ServletContext object.
The scoping attribute is powerful. For example, in a shopping cart application, you could store a user's cart as a bean with the scope of "session." Then, as the user goes through the site, each JSP page will have access to the shopping cart via the user's session.
Back to our Fibonnaci example. We can create a JavaBean that is responsible for doing the Fibonnaci calculation. This code will be in com.customware.fib.Fibonnaci. Now we can change our JSP page to use this Fibonnaci bean. Not only will we be able to reuse this code, but the JSP is a lot shorter (see Listing 2).
We now have some functionality in a JavaBean (see Listing 3), and a JSP that uses the functionality via the following:
A scriptlet can use the Bean via the id
attribute, e.g., fib.calculateFibonacci( 6 )
Creating "Access Beans" Via the Action
The bean we used is a plain vanilla object that has the method to calculate Fibonnaci. In the real world, the business logic could be an EJB. There are multiple steps to go through when you work with an EJB:
- Log in to the EJB application server (e.g., JNDI).
- Find the factory (home) object that will allow you to create or find an EJB (depending on the type).
- Retrieve a remote reference to an actual bean via the home object.
Instead of having all of this code in the JSP itself, it is better to abstract it out. One way to do this is to write an object that "wraps" the functionality you wish to use. I call this an "Access Bean," a façade for the functionality.
We will create an Access Bean for the Fibonnaci example. Instead of just creating a bean and using its functionality (as we have done so far), we will create an Access Bean built for the Web. The class com.customware.fib.FibonnaciAccessBean (see Listing 4) will wrap around the Fibonnaci object we have created, and we will just change the interface. This class will understand an HttpRequest object being passed to it, and will use an HTTP parameter "num" as input to the Fibonnaci equation. We can then access the accessbean.jsp (Listing 5), and use the to pass in different values.
Using an Access Bean gives a simple interface to the JSP while using the real functionality of the Fibonnaci object. Another client, such as a Swing UI application, would be able to reuse the Fibonnaci object and could even create its own Access Bean for its own purposes.
Tag Libraries: The Savior for a Large Web Site?
So far, we have abstracted business logic from within the JSP out into its own class. This is a good step, offering better reuse and a cleaner JSP for designers. If you are working on a small project, or a project where the designers are also developers, this may be enough abstraction for you. Large projects have teams of developers, HTML designers, and people in a production team. Having the tags and a small amount of Java code confuses and intimidates the designers, and even the production associates.
Having these groups all working in one file (a JSP page) is also a bad idea, as you could be locked out of a file while someone else is working in it. Some revision control systems allow multiple people to work on the same file and "merge" the results, but it can get messy.
This is where Tag Libraries come into play. The JSP framework allows you to create your own tags.
Let's say that in an e-commerce site the client wants to have a "Welcome Dion Almaer!" message on some pages. We could create a bean with a method getWelcomeMessage(), but we would have to put the useBean tag and method call in all of the pages.
What if we create a tag that can be used within the page? With Tag Library extensions, we can agree on an XML tag that can be used in the JSP pages. Now developers and designers can agree on a tag named . When this tag is found in the JSP, the engine will replace it with the name of the user who is looking at the site.
Together, the design and development team can go through the site, agreeing on all the tags they need. A contract could be written up that lays out every tag, what attributes each tag has, and nesting conditions.
To create a custom tag, follow these steps:
Step One: Name your tag(s), attributes of the tags, etc.
Step Two: Define the tags in a "Tag Library Definition" file.
Step Three: Create a Java class to handle each tag.
Step Four: Package everything together.
Step Five: Tell each JSP page you want to use these custom tags.
Step Six: Use the tags.
Now we will go through each of the steps, building on the Fibonnaci example.
Step One: Planning
The tag we will create will need to take input on what number to do the calculation on. This will work well for the designers: 10.
- The tag name is "calculate."
- There is one attribute named "input" that takes a number.
- The body will be ignored.
If the body will be ignored, why bother having one at all? We could easily have the tag:
A reason to allow the body is again for the designers. There should be a body for all tags that output text in the JSP. This allows the designer to put some dummy data in between the tags. If you view the JSP page in your browser, the tags will be ignored, but the dummy data will be shown. This allows the designer to see what the page will look like without having to go through a Web server. The example would be John Doe if this rule was followed.
The tag looks like "fib:calculate" due to the XML namespace. When declaring tags for your JSP, you define a name space for the set of tags. This allows two tag libraries that have the same tag name to be used. For example, and .
Step Two: Creating the Tag Library Description File
The tag library descriptor is an XML file. Listing 6 shows the descriptor for our example.
The file first sets up global information about the set of tags, such as a description of the library, the version of JSP that it is for, etc. Then, each tag has its own . In this section, we define the name of the tag, the Java class that represents the tag, and the attributes the tag can take.
Step Three: Create a Tag Class
We now have to write the code that is run when the tag is encountered in the JSP page. This class needs to implement one of two interfaces.
javax.servlet.jsp.tagext.Tag—This interface specifies callback methods the JSP engine will call at various points in the tag life cycle (as well as a few other methods).
doStartTag()—In many cases, the tag handler only needs to use the tag handler's method doStartTag() that is invoked when the start tag is encountered. This method returns an indication of whether the body of the action should be evaluated (EVAL_BODY_INCLUDE or EVAL_BODY_TAG) or not (SKIP_BODY). In our example, because the entire action will be enclosed in one tag, we simply return SKIP_BODY.
doEndTag()—This is similar to doStartTag(), except it is invoked when the end tag of the action is encountered. If this method returns EVAL_PAGE, the rest of the page continues to be evaluated. If this method returns SKIP_PAGE, the rest of the page is not evaluated and the request is completed. If this request was forwarded or included from another page (or Servlet), only the current page evaluation is completed. In our example, we will calculate the Fibonnaci equation and print the result back to the page at this point.
javax.servlet.jsp.tagext.BodyTag—Most tags can implement the Tag interface and be done. In the rare case where you need to manipulate the body, have nested tags, maybe do some iteration within the tag, or something similar, you can implement the BodyTag interface. This interface extends the Tag interface (so you still get the doStart/EndTag()), and gives you doInitBody() and doAfterBody(). These are just more life-cycle callback methods.
doInitBody() is called after doStartTag(), and after the body of the tag has been converted to a BodyContent object and passed via setBodyContent(BodyContent bc). doAfterBody() is called after the body has been evaluated.
Some base classes are provided. They "do the right thing" for many of the methods and allow the convenience of not having to implement all of the methods in the interface. It is very common to use them (similar to extending HttpSession vs. implementing the Servlet interface). Both interfaces have a corresponding base class: TagSupport implements Tag, and BodySupport implements BodyTag.
Our example does not need to manipulate the body (ignoring it doesn't count), so we can extend the TagSupport class. Let's take a look at Listing 7. The Java code has the following structure:
Step Four: Package Everything Together
- Import the relevant classes (javax.servlet.jsp.*, javax.servlet.jsp.tagext.*).
- Extend the TagSupport class.
- Have setter and getter for the attributes in the tag. In our case, we have one attribute "input," hence, the setInput(String input), and getInput(). These methods follow JavaBean conventions. Every attribute must have a set and get .
- Implement the doStartTag() and the doEndTag() callbacks.
- doStartTag(): All we are doing here is telling the engine to ignore the body, returning SKIP_BODY.
- doEndTag(): Here we use the number we set in the setInput() method. We print the result back to the page via the "out" file handle. The following code returns the handle: pageContext.getOut(). Finally, we tell the engine to continue evaluating the rest of the page via return EVAL_PAGE;.
The JSP server needs to be able to find the tag library descriptor file in the file system, and the Java classes have to be in the CLASSPATH
A good approach to packaging your application is to use the Sun Web Application standard (.war files). The directory structure is shown in Table 2. (To see all of the files, download the fibonnaci.war file from here.)
|Table 2. Web application structure.
||Place all of the Java .class files e.g., WEB-INF\classes\com\customware\fib\FibonnaciCalculateTag.class
||Place the Tag Library Descriptor in the WEB-INF directory
Step Five: Declaring the Tag Library in the JSP
The only change needed for the Java Server Page to see the new tags is to add a Java directive: <%@ taglib uri="/fib.tld" prefix="fib" %>.
The URI points to where the .tld file is, and the prefix sets the namespace for the tags.
Step Six: Use the Custom Tag
Now we can use the tag we defined in the planning stage. Look at Listing 8 to see the usage at work: 10.
Large, dynamic Web sites need people with different skills working on the front end of an application. Tag Libraries allow the page writer and Java programmer to add one more layer of abstraction to the problem solution. This helps enormously since the logic code, like JavaBeans, can be separated from the presentation.
However, Tag Libraries take the useBean approach one step further, as the page writer now knows nothing at all about the underlying Java code and can simply use a set of predefined tags that most HTML writers find natural.