[ Team LiB ] Previous Section Next Section

17.5 Precompiling JSP Pages

To avoid hitting your site visitors with the delay caused by the conversion of a JSP page into a servlet on the first access, you can precompile all pages in the application. Another use of precompilation is if you don't want anyone to change the pages in a JSP-based application after the application has been deployed. In this case, you can precompile all pages, define URL mappings for all JSP pages in the application deployment descriptor, and install just the Java class files for the compiled pages. We look at both these scenarios in this section.

One way of precompiling all pages in an application is to simply run through the application in a development environment and make sure you hit all pages. You can then copy the class files together with all the rest of the application to the production server when you deploy the application. Where the class files are stored varies between containers. Tomcat stores all JSP page implementation classes in its work directory by default, in a subdirectory per web application. As long as the modification dates of the class files are more recent than the corresponding JSP pages, the production server uses the copied class files.

The JSP specification also defines a special request parameter that can give the JSP container a hint that the page should be compiled without letting the page process the request. An advantage of using this method is that you can automatically invoke each page, perhaps using a simple load-testing tool, without having to provide all the regular request parameters the pages use. Because the pages aren't executed, application logic that requires pages to be invoked in a certain order or enforces similar rules can't interfere with the compilation. The request parameter name is jsp_precompile, and valid values are true and false, or no value at all. In other words, the following URLs are all valid:

/ora/ch17/applet.jsp?jsp_precompile
/ora/ch17/applet.jsp?jsp_precompile=true
/ora/ch17/applet.jsp?jsp_precompile=false

The third example is not very useful, because if the parameter value is false, the request is simply ignored. A JSP container that receives a request like the ones in the first and second example should compile the JSP page (go through the translation phase) but not let the page process the request. Most JSP containers support this feature, even though the specification doesn't require it. A compliant JSP container is allowed to ignore the compilation request, as long as it doesn't let a JSP page process a request that includes a jsp_precompile parameter with the value true or no value at all.

When you have compiled the JSP pages, you can package your application without the JSP pages themselves, using only the generated servlet class files. You do this by adding URL mapping definitions in the application deployment descriptor, so that a request for a certain JSP page is served directly by the corresponding servlet instead:

<web-app>
  ...
  <servlet>
    <servlet-name>easy</servlet-name>
    <servlet-class>org.apache.jsp.ch5.easy_jsp</servlet-class>
  </servlet>
  ...
  <servlet-mapping>
    <servlet-name>easy</servlet-name>
    <url-pattern>/ch5/easy.jsp</url-pattern>
  </servet-mapping>
  ...
</web-app>

The <servlet> element maps the servlet class name to a symbolic name. The class name for a JSP page implementation class (the generated servlet) is container-dependent; this example uses the type of class name Tomcat creates. The <servlet-mapping> element then tells the container to invoke the class when it receives a request matching the defined pattern, which is the context-relative path for the JSP page.

There are two reasons why you might want to precompile the JSP pages and define URL mappings for them. One reason is that using the servlet class directly is slightly faster, since the container doesn't have to go through the JSP container code to figure out which servlet class to use. The other reason is that if you don't include the JSP pages in the application packet, no one can change the application. This can be an advantage if you resell prepackaged JSP-based applications.

Doing all this by hand is a lot of work. Fortunately, the Apache Ant Java-based build tool (available at http://ant.apache.org/) combined with the JspC tool that's part of Tomcat can do all this for you. Ant uses an XML file that defines the tasks needed to build an application from source files. JspC (JSP Compiler) is a Java class that uses Tomcat's JSP container to generate servlet classes for all JSP pages in an application and creates the URL mapping declarations automatically.

After you have installed Ant, create an Ant build file, named build.xml, with this content (you find a file like this in the root directory for the examples application):

<project name="Precompile" default="all" basedir=".">

  <target name="jspc"> 
    <taskdef classname="org.apache.jasper.JspC" name="jasper2" > 
      <classpath id="jspc.classpath"> 
        <pathelement location="${java.home}/../lib/tools.jar"/> 
        <fileset dir="${tomcat.home}/server/lib"> 
          <include name="*.jar"/> 
        </fileset> 
        <fileset dir="${tomcat.home}/common/lib"> 
          <include name="*.jar"/> 
        </fileset> 
      </classpath> 
    </taskdef> 

    <jasper2 
             validateXml="false" 
             uriroot="${webapp.path}" 
             webXmlFragment="${webapp.path}/WEB-INF/generated_web.xml" 
             outputDir="${webapp.path}/WEB-INF/src" /> 
  </target> 

  <target name="compile">
    <mkdir dir="${webapp.path}/WEB-INF/classes"/>
    <mkdir dir="${webapp.path}/WEB-INF/lib"/>

    <javac destdir="${webapp.path}/WEB-INF/classes"
           optimize="off"
           debug="on" failonerror="false"
           srcdir="${webapp.path}/WEB-INF/src" 
           excludes="**/*.smap">
      <classpath>
        <pathelement location="${webapp.path}/WEB-INF/classes"/>
        <fileset dir="${webapp.path}/WEB-INF/lib">
          <include name="*.jar"/>
        </fileset>
        <pathelement location="${tomcat.home}/common/classes"/>
        <fileset dir="${tomcat.home}/common/lib">
          <include name="*.jar"/>
        </fileset>
        <pathelement location="${tomcat.home}/shared/classes"/>
        <fileset dir="${tomcat.home}/shared/lib">
          <include name="*.jar"/>
        </fileset>
      </classpath>
      <include name="**" />
      <exclude name="tags/**" />
    </javac>
  </target>

  <target name="all" depends="jspc,compile">
  </target>
</project>

The build file first defines a target (build step) named jspc that uses the JspC tool to generate Java source files for all JSP pages in a web application. The source files are created in the WEB-INF/src directory. JspC also generates <servlet> and <servlet-mapping> elements for each JSP page and places them in WEB-INF/generated_web.xml. The second target, named compile, creates the WEB-INF/classes and WEB-INF/lib directories if they don't exist, and then compiles all the generated Java source files for the JSP pages with the class files ending up in the WEB-INF/classes directory.

To process this build file, use a command shell and change directory to where the build.xml file is located. For the examples application, this is webapps/ora, if you have installed the examples as described in Chapter 4. Then run this command:

C:\> ant -Dtomcat.home=%CATALINA_HOME% -Dwebapp.path=.

If the build.xml file is located in some other directory than the application root directory, specify the real path for the -Dwebapp.path argument. Running this command creates compiled versions of all JSP pages and places the class files under WEB-INF/classes and the mapping elements in the WEB-INF/generated_web.xml file. If you have a web.xml file with other configuration setting, just copy the contents of the generated_web.xml file into the real web.xml file; if you don't have any other setting, just rename the file to web.xml and add an XML declaration and the root element, as shown in Appendix F. You can now remove the JSP pages, because the mappings tells the container to invoke the precompiled class files directly.

There is one more thing you need to be aware of. JspC is not part of the JSP specification; it's a tool that is available only for Tomcat. Other containers may contain similar tools, though. Using JspC or a similar tool works fine as long as you compile and deploy the generated servlet classes using the same web container product. But a web container uses its own internal classes in the generated servlets, which means that if you generate the servlets with one web container (such as Tomcat) and deploy them in another (such as New Atlanta's ServletExec), you must also deliver the internal classes. For Tomcat, these classes are packaged in the common/lib/jasper-runtime.jar file, and you're free to deliver this JAR file with your application. If you use a precompilation tool that belongs to some other container, you need to read the documentation and see where the internal classes it needs are stored. Also make sure the tool has a license that allows you to redistribute those classes before you bundle them with your application.

    [ Team LiB ] Previous Section Next Section