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
Pages: 1, 2

Understanding How <jc:cache> Works

For each occurrence of the <jc:cache> tag in a JSP page, the JSP container creates a CacheTag instance, which is prepared for handling the tag. It is the responsibility of the JSP container to call the setJspContext(), setParent(), and setJspBody() methods that CacheTag inherits from SimpleTagSupport. The JSP container also calls a setter method for each attribute of the handled tag. The setId() and setScope() methods store the attribute values into the private fields, which are initialized with default values in the CacheTag() constructor:

package com.devsphere.articles.jspcache;

import javax.servlet.ServletContext;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;

import java.io.IOException;
import java.io.StringWriter;

public class CacheTag extends SimpleTagSupport {
    public static final String CACHE_ENABLED
        = "com.devsphere.articles.jspcache.enabled";
    private String id;
    private int scope;
    private boolean cacheEnabled;

    public CacheTag() {
        id = null;
        scope = PageContext.APPLICATION_SCOPE;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setScope(String scope) {
        this.scope = JspUtils.checkScope(scope);
    }
    ...
}

The setScope() method calls JspUtils.checkScope() to verify the value of the scope attribute, which is converted from String to int:

...
public class JspUtils {
    ...
    public static int checkScope(String scope) {
        if ("page".equalsIgnoreCase(scope))
            return PageContext.PAGE_SCOPE;
        else if ("request".equalsIgnoreCase(scope))
            return PageContext.REQUEST_SCOPE;
        else if ("session".equalsIgnoreCase(scope))
            return PageContext.SESSION_SCOPE;
        else if ("application".equalsIgnoreCase(scope))
            return PageContext.APPLICATION_SCOPE;
        else
            throw new IllegalArgumentException(
                "Invalid scope: " + scope);
    }

}

Once the CacheTag instance is prepared to handle the tag, the JSP container calls the doTag() method, which obtains a JSP context with getJspContext(). This object is casted to PageContext in order to call getServletContext(). The servlet context is used to obtain the value of the initialization parameter that indicates whether the caching mechanism is enabled or not. If the cache is enabled, doTag() tries to get the cached page fragment, using the values of the id and scope attributes. If the page fragment hasn't been cached yet, doTag() uses getJspBody().invoke() to execute the JSP code wrapped between <jc:cache> and </jc:cache>. The output generated by the JSP body is buffered in a StringWriter and is obtained with toString(). At this point, doTag() calls the setAttribute() method of the JSP context to create a JSP variable that will hold the cached content, which may contain JSP expressions (${...}). Those expressions are evaluated with JspUtils.eval() before the content is outputted with jspContext.getOut().print(). All of these actions take place only if the cache is enabled. Otherwise, doTag() just executes the JSP body with getJspBody().invoke(null) and the output is not cached:

...
public class CacheTag extends SimpleTagSupport {
    ...
    public void doTag() throws JspException, IOException {
        JspContext jspContext = getJspContext();
        ServletContext application
            = ((PageContext) jspContext).getServletContext();
        String cacheEnabledParam
            = application.getInitParameter(CACHE_ENABLED);
        cacheEnabled = cacheEnabledParam != null
            && cacheEnabledParam.equals("true");
        if (cacheEnabled) {
            String cachedOutput
                = (String) jspContext.getAttribute(id, scope);
            if (cachedOutput == null) {
                StringWriter buffer = new StringWriter();
                getJspBody().invoke(buffer);
                cachedOutput = buffer.toString();
                jspContext.setAttribute(id, cachedOutput, scope);
            }
            String evaluatedOutput = (String) JspUtils.eval(
                cachedOutput, String.class, jspContext);
            jspContext.getOut().print(evaluatedOutput);
        } else
            getJspBody().invoke(null);
    }
    ...
}

Note that a single call of JspUtils.eval() evaluates all ${...} expressions, since a text containing multiple ${...} constructs is an expression, too. Each cached fragment can be processed as a big JSP expression.

The isCacheEnabled() method returns the value of the cacheEnabled flag, which is initialized in doTag():

...
public class CacheTag extends SimpleTagSupport {
    ...
    public boolean isCacheEnabled() {
        return cacheEnabled;
    }

}

The <jc:cache> tag allows the page developer to choose the IDs of the cached page fragments. This opens the possibility to cache a page fragment that is shared by multiple JSP pages, which is helpful when you reuse the JSP code, but this also requires some naming convention to avoid conflicts. If you want to avoid this side effect, you can modify the CacheTag class to include the URL within the ID automatically.

Understanding What <jc:dynamic> Does

Each <jc:dynamic> tag is handled by an instance of the DynamicTag class, whose setExpr() method stores the value of the expr attribute into a private field. The doTag() method builds a JSP expression, adding the ${ prefix and the } suffix to the value of the expr attribute. Then, doTag() uses findAncestorWithClass() to find the CacheTag handler of the <jc:cache> element that contains the <jc:dynamic> tag. If the ancestor isn't found or the caching is disabled, the JSP expression is evaluated with JspUtils.eval() and then its value is outputted. Otherwise, doTag() outputs the unevaluated expression:

package com.devsphere.articles.jspcache;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

import java.io.IOException;

public class DynamicTag extends SimpleTagSupport {
    private String expr;

    public void setExpr(String expr) {
        this.expr = expr;
    }

    public void doTag() throws JspException, IOException {
        String output = "${" + expr + "}";
        CacheTag ancestor = (CacheTag) findAncestorWithClass(
            this, CacheTag.class);
        if (ancestor == null || !ancestor.isCacheEnabled())
            output = (String) JspUtils.eval(
                output, String.class, getJspContext());
        getJspContext().getOut().print(output);
    }

}

Analyzing the above code fragments, you'll observe that <jc:cache> and <jc:dynamic> cooperate in order to implement a solution that is as efficient as possible. If the cache is enabled, page fragments are buffered together with the JSP expressions that are generated by <jc:dynamic> and evaluated by CacheTag. If the cache is disabled, the buffering is not necessary and <jc:cache> just executes its JSP body, letting DynamicTag evaluate the JSP expressions. It is useful to disable the caching, especially during development when the content is changed and the JSP pages are recompiled. Of course, the caching should be enabled in a production environment.

Summary

Content caching is a very easy way to improve the performance of your web applications. This article focused on customizing the cached content for each user or request, using the JSP Expression Language. The simple tag library presented throughout the article is suitable for small web apps and could be enhanced for medium ones. If you develop large enterprise applications, you should consider using a framework that provides a better caching mechanism than the use of the JSP variables, but you'll probably still find useful the customization technique based on the EL API.

Resources

Andrei Cioroianu is the founder of Devsphere and an author of many Java articles published by ONJava, JavaWorld, and Java Developer's Journal.


Return to ONJava.com