Compile JSPs On Application Startup
The first request to a JSP typically has a processing delay as the JSP container compiles the JSP. Specifically, the container generates Java source code for a servlet and compiles the source code to a class file. You can prevent your users from experiencing this delay by compiling the JSPs before they are requested.
The Tomcat manual
describes how to compile JSPs at build time. An Ant target runs the Japser JSP
Engine to convert JSPs to servlet code. Another Ant target compiles the
servlet code to class files. Servlet declarations and mappings for the
generated servlets must be merged into the web.xml
file. I’m going to
present another solution that compiles JSPs when the application starts.
The JSP specification says a container must accept the request parameter
jsp_precompile
on a request to a JSP. The parameter can have values true
or false
, or no value at all. If the parameter has value true
or no value,
then it is a suggestion to the container to compile the JSP. Even if container
chooses to ignore this suggestion, it must not let the page process the
request.
A
custom servlet context listener
compiles all JSPs in the Web application when the application starts. To use
it, define a listener in the web.xml
file:
<listener>
<listener-class>com.github.pukkaone.jsp.JspCompileListener</listener-class>
</listener>
Details
The JspCompileListener
class implements the
ServletContextListener
interface so it receives a notification when the Web application starts.
public class JspCompileListener implements ServletContextListener {
private ServletContext servletContext;
private HttpServletRequest request = createHttpServletRequest();
private HttpServletResponse response = createHttpServletResponse();
The contextInitialized
method receives a notification when the application
starts.
public void contextInitialized(ServletContextEvent event) {
servletContext = event.getServletContext();
compileJspsInDirectory("/");
}
The compileJspsInDirectory
method finds and compiles all JSPs in a directory.
It recursively descends into subdirectories and processes JSPs found there.
@SuppressWarnings("unchecked")
private void compileJspsInDirectory(String dirPath) {
Set<String> paths = servletContext.getResourcePaths(dirPath);
for (String path : paths) {
if (path.endsWith(".jsp")) {
RequestDispatcher requestDispatcher =
servletContext.getRequestDispatcher(path);
if (requestDispatcher == null) {
// Should have gotten a RequestDispatcher for the path
// because the path came from the getResourcePaths() method.
throw new Error(path + " not found");
}
try {
servletContext.log("Compiling " + path);
requestDispatcher.include(request, response);
} catch (Exception e) {
servletContext.log("include", e);
}
} else if (path.endsWith("/")) {
// Recursively process subdirectories.
compileJspsInDirectory(path);
}
}
}
The call to the
RequestDispatcher.include
method sends a request to a JSP. One of the arguments must be a
HttpServletRequest
implementation that returns jsp_precompile
for the query string. I could
have written a class providing stub implementations for the HttpServletRequest
methods, but the interface has so many methods that the code to create a
JDK dynamic proxy
implementing the interface is more concise. Another JDK dynamic proxy
implements the
HttpServletResponse
interface in a similar fashion.
private HttpServletRequest createHttpServletRequest() {
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getQueryString")) {
return "jsp_precompile";
}
return null;
}
};
return (HttpServletRequest) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class<?>[] { HttpServletRequest.class },
handler);
}