ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Caching Dynamic Content with JSP 2.0 Caching Dynamic Content with JSP 2.0

by Andrei Cioroianu
01/05/2005

Content caching is one of the most common optimization techniques used in web applications, and it can be implemented easily. For example, you can use a custom JSP tag--let's call it <jc:cache>--to wrap every page fragment that must be cached between <jc:cache> and </jc:cache>. Any custom tag can control when its body (i.e. the wrapped page fragment) is executed, and the dynamic output can be captured. The <jc:cache> tag lets the JSP container (e.g. Tomcat) generate the content only once, storing each cached fragment as a JSP variable in the application scope. Every time the JSP page is executed, the custom tag inserts the cached page fragment without re-executing the JSP code that generated the output. A tag library developed as part of the Jakarta project uses this technique, and it works fine when the cached content doesn't need to be customized for each user or request.

This article improves the technique described above, allowing the JSP page to customize the cached content for each request or user, using the JSP 2.0 Expression Language (EL). Cached page fragments can contain JSP expressions that are not evaluated by the JSP container, the custom tag evaluating these expressions each time the page is executed. Therefore, the creation of the dynamic content is optimized, but the cached fragments can have pieces of content that are generated for each request using the native expression language of JSP. This is possible with the help of the JSP 2.0 EL API, which exposes the expression language to the Java developer.

Content Caching Versus Data Caching

Content caching is not the only option. For example, data extracted from a database can be cached, too. In fact, data caching can be more efficient, since it stores the information without the HTML markup, requiring less memory. In many situations, however, content caching is easier to implement. Let's suppose you have lots of business objects producing some complex data, using significant CPU resources. You also have JSP pages that present the data. Everything works well until one day when the server's load suddenly increases, which requires an urgent solution. Building a caching tier between those business objects and the presentation tier can be a very elegant and efficient solution, but it could be much quicker and easier to modify the JSP pages, caching the dynamic content. Changes in the application's business logic usually require more work and more testing than simple editing of the JSP pages. In addition, there are fewer changes in the web tier when one page aggregates information from multiple sources. The problem is that the cache sometimes needs to be invalidated when the information becomes stale, and the business objects better know when this happens. Therefore, when choosing to implement content caching, data caching, or another optimization technique, you have to take into account many factors, which are sometimes specific to the application you are building.

Data caching and content caching do not necessarily exclude each other. They can be used together; for example, in database-driven applications. Data extracted from the database and the HTML that presents the data can be cached separately. This is similar to using some sort of templates, which are generated on the fly using JSP. The techniques based on the EL API discussed in this article show how you could use the JSP EL to insert the data into the templates for presentation.

Using JSP Variables to Cache Dynamic Content

Every time you implement a caching mechanism, you need a way to store the cached objects, which are strings in the case presented in this article. You could use an object-caching framework, or you might implement a custom caching solution, using Java maps. JSP already provides the so-called "scoped attributes" or "JSP variables," which offer the ID-object mappings needed by the caching mechanism. It doesn't make sense to use the page or request scopes, but the application scope is a good place for storing the cached content, since it's shared by all users and all pages. The session scope can also be used when you need one cache per user, but this isn't very efficient. The JSTL tag library can be used to cache content, using JSP variables as in the following example:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:if test="${empty cachedFragment}">
    <c:set var="cachedFragment" scope="application">
        ...
    </c:set>
</c:if>

The cached page fragment can be outputted with:

${applicationScope.cachedFragment}

What happens if the cached fragment needs to be customized for each request? For example, if you want to include a counter, you need to cache two fragments:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:if test="${sessionScope.counter == null}">
    <c:set var="counter" scope="session" value="0"/>
</c:if>
<c:set var="counter" value="${counter+1}" scope="session"/>

<c:if test="${empty cachedFragment1}">
    <c:set var="cachedFragment1" scope="application">
        ...
    </c:set>
</c:if>

<c:if test="${empty cachedFragment2}">
    <c:set var="cachedFragment2" scope="application">
        ...
    </c:set>
</c:if>

Then, you can output the cached content with:

${cachedFragment1} ${counter} ${cachedFragment2}

It is much easier to cache the page fragments that need customization with the help of a specialized tag library. As already mentioned, the cached content can be wrapped between a start tag (<jc:cache>) and an end tag (</jc:cache>), while each customization is represented by another tag (<jc:dynamic expr="..."/>) that outputs a JSP expression (${...}). The dynamic content is cached with JSP expressions that are evaluated each time the cached content is outputted. You'll see how this is implemented in the following sections of the article. The counter.jsp page caches a page fragment containing a counter that is incremented each time the user refreshes the page:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="jc" uri="http://devsphere.com/articles/jspcache" %>

<c:if test="${sessionScope.counter == null}">
    <c:set var="counter" scope="session" value="0"/>
</c:if>
<c:set var="counter" value="${counter+1}" scope="session"/>

<jc:cache id="cachedFragmentWithCounter">
    ... <jc:dynamic expr="sessionScope.counter"/> ...
</jc:cache>

Related Reading

Head First Servlets and JSP
Passing the Sun Certified Web Component Developer Exam
By Bryan Basham, Kathy Sierra, Bert Bates

JSP variables are easy to use and are a good content-caching solution for simple web apps. The lack of control over the cache's size may be a problem, though, if your application produces large amounts of dynamic content. A dedicated caching framework would provide a more robust solution, allowing you to monitor the cache, limit the cache's size, control the caching policy, and so on.

Using the JSP 2.0 Expression Language API

JSP containers (such as Tomcat) evaluate the expressions from the JSP pages using the EL API, which you can use in your Java code, too. This allows you to work with the JSP EL outside of your web pages, for example, in XML files, text-based resources, and custom scripts. The EL API is also useful when you want to control when the expressions from a web page are evaluated or when you build expressions programmatically. For example, cached page fragments can contain JSP expressions for customization and the EL API will be used to evaluate and reevaluate those expressions each time the cached content is outputted.

The example application (see Resources below) provided with this article includes a Java class (JspUtils) that contains a method named eval(), which takes three parameters: a JSP expression, the expected type of the expression's value, and a JSP context object. The eval() method gets an ExpressionEvaluator from the JSP context and calls the evaluate() method, passing the expression, the expected type, and a variable resolver that is obtained from the JSP context. The JspUtils.eval() method returns the value of the expression:

package com.devsphere.articles.jspcache;

import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.ExpressionEvaluator;

import java.io.IOException;

public class JspUtils {
    public static Object eval(
            String expr, Class type, JspContext jspContext)
            throws JspException {
        try {
            if (expr.indexOf("${") == -1)
                return expr;
            ExpressionEvaluator evaluator
                = jspContext.getExpressionEvaluator();
            return evaluator.evaluate(expr, type,
                jspContext.getVariableResolver(), null);
        } catch (ELException e) {
            throw new JspException(e);
        }
    }
    ...
}

Note that JspUtils.eval() is basically a wrapper around the standard ExpressionEvaluator. If expr doesn't contain ${, the JSP EL API isn't used, since there are no JSP expressions.

Creating the TLD

The JSP tag library needs a Tag Library Descriptor (TLD) file that specifies the names of the custom tags, their attributes, and the Java classes that handle the custom tags. The jspcache.tld file describes the two custom tags. The <jc:cache> tag has two attributes: the id for the cached page fragment and the JSP scope where the content should be stored. The <jc:dynamic> tag has only one attribute, which should be a JSP expression that must be evaluated each time the cached fragment is outputted. The TLD file maps the two custom tags to the CacheTag and DynamicTag classes, which are presented in the following sections:

<?xml version="1.0" encoding="UTF-8" ?>

<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 web-jsptaglibrary_2_0.xsd"
    version="2.0">

    <tlib-version>1.0</tlib-version>
    <short-name>jc</short-name>
    <uri>http://devsphere.com/articles/jspcache</uri>

    <tag>
        <name>cache</name>
        <tag-class>com.devsphere.articles.jspcache.CacheTag</tag-class>
        <body-content>scriptless</body-content>
        <attribute>
            <name>id</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>scope</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
    </tag>

    <tag>
        <name>dynamic</name>
        <tag-class>com.devsphere.articles.jspcache.DynamicTag</tag-class>
        <body-content>empty</body-content>
        <attribute>
            <name>expr</name>
            <required>true</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
    </tag>

</taglib>

The TLD file is declared in the web application descriptor (web.xml), which also contains an initialization parameter that indicates whether the cache is enabled or not:

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app 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 web-app_2_4.xsd"
    version="2.4">

    <context-param>
        <param-name>com.devsphere.articles.jspcache.enabled</param-name>
        <param-value>true</param-value>
    </context-param>

    <jsp-config>
        <taglib>
            <taglib-uri>http://devsphere.com/articles/jspcache</taglib-uri>
            <taglib-location>/WEB-INF/jspcache.tld</taglib-location>
        </taglib>
    </jsp-config>

</web-app>

Pages: 1, 2

Next Pagearrow