如何让 Tomcat 在启动时预编译 JSP?

Posted

技术标签:

【中文标题】如何让 Tomcat 在启动时预编译 JSP?【英文标题】:How can I make Tomcat pre-compile JSPs on startup? 【发布时间】:2010-10-04 14:42:21 【问题描述】:

我们在我工作的地方同时使用 Apache Tomcat 6.0 和 Jetty 6。我们主要使用 Jetty 进行测试(它非常适合运行嵌入式 JUnit 测试)和 Tomcat 进行生产。

默认情况下,Tomcat 在用户请求时即时编译 JSP。但这会导致第一次命中的性能下降。它还在 Tomcat 的 JSP 编译器中突出显示 bizarre bugs。

Tomcat documentation 给出了在构建时使用 Ant 预编译 JSP 的建议(还有一个 Maven 插件)......但是生成的 WAR 包含特定于 Tomcat 的内容,例如PageContextImpl.proprietaryEvaluate,所以我们不能在 Jetty 中使用它。

是否有一些标志或设置我们可以在某个地方使用来强制 Tomcat 在 WAR 初始化后立即预编译所有 JSP?我们准备在启动时再等一会儿。

提前:我知道有一种方法可以准确地预编译 one JSP,方法是在 web.xml 中为一个 JSP 显式标识 /servlet/load-on-startup 标记。但是对于几十个甚至几百个 JSP 来说就变得难以管理了。

【问题讨论】:

这里有人想做和你一样的事情:预编译 JSP、Tomcat 或 Jetty,而不是 Ant。也许this 也会帮助你。我自己没试过。 链接到奇怪的错误是错误的。我认为 https 是在没有冒号的情况下输入的,混淆了它...... 我会使用两个构建或两个输出 - 一个用于码头,一个用于 tomcat,这样每次重新启动时不需要等待 tomcat 编译所有 jsps 【参考方案1】:

http://www.devshed.com/c/a/BrainDump/Tomcat-Capacity-Planning/


    project name="pre-compile-jsps" default="compile-jsp-servlets">

  <!-- Private properties. -- >
  <property name="webapp.dir" value="$basedir/webapp-dir"/>
  <property name="tomcat.home" value="/opt/tomcat"/>
  <property name="jspc.pkg.prefix" value="com.mycompany"/>
  <property name="jspc.dir.prefix" value="com/mycompany"/> 

  <!-- Compilation properties. -->
  <property name="debug" value="on"/> 
  <property name="debuglevel" value="lines,vars,source"/>
  <property name="deprecation" value="on"/>
  <property name="encoding" value="ISO-8859-1"/>
  <property name="optimize" value="off"/>
  <property name="build.compiler" value="modern"/>
  <property name="source.version" value="1.5"/> 

  <!-- Initialize Paths. -->
  <path id="jspc.classpath">
    <fileset dir="$tomcat.home/bin">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="$tomcat.home/server/lib">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="$tomcat.home/common/i18n">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="$tomcat.home/common/lib">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="$webapp.dir/WEB-INF">
      <include name="lib/*.jar"/>
    </fileset>
    <pathelement location="$webapp.dir/WEB-INF/classes"/>
    <pathelement location="$ant.home/lib/ant.jar"/>
    <pathelement location="$java.home/../lib/tools.jar"/>
  </path>
  <property name="jspc.classpath" refid="jspc.classpath"/> 

  <!--========================================================== -->
  <!-- Generates Java source and a web.xml file from JSP files.                     -->
  <!-- ==========================================================
-->
  <target name="generate-jsp-java-src"> 
    <mkdir dir="$webapp.dir/WEB-INF/jspc-src/$jspc.dir.prefix"/>
    <taskdef classname="org.apache.jasper.JspC" name="jasper2">
      <classpath>
        <path refid="jspc.classpath"/>
      </classpath>
    </taskdef>
    <touch file="$webapp.dir/WEB-INF/jspc-web.xml"/>
    <jasper2 uriroot="$webapp.dir"
             package="$jspc.pkg.prefix" 
          webXmlFragment="$webapp.dir/WEB-INF/jspc-web.xml" 
             outputDir="$webapp.dir/WEB-INF/jspc-src/$jspc.dir.prefix"
             verbose="1"/>
  </target> 

  <!-- ========================================================== -->
  <!-- Compiles (generates Java class files from) the JSP servlet -->
  <!-- source code that was generated by the JspC task.            -->
  <!-- ========================================================== -->
  <target name="compile-jsp-servlets" depends="generate-jsp-java-src">
    <mkdir dir="$webapp.dir/WEB-INF/classes"/>
    <javac srcdir="$webapp.dir/WEB-INF/jspc-src"
           destdir="$webapp.dir/WEB-INF/classes"
           includes="**/*.java"
           debug="$debug"
           debuglevel="$debuglevel"
           deprecation="$deprecation"
           encoding="$encoding"
           optimize="$optimize"
           source="$source.version">
      <classpath>
        <path refid="jspc.classpath"/>
      </classpath>
    </javac>
  </target> 

  <!-- ========================================================= -->
  <!-- Cleans any pre-compiled JSP source, classes, jspc-web.xml -->
  <!-- ========================================================= -->
  <target name="clean">
    <delete dir="$webapp.dir/WEB-INF/jspc-src"/>
    <delete dir="$webapp.dir/WEB-INF/classes/$jspc.dir.prefix"/>
    <delete file="$webapp.dir/WEB-INF/jspc-web.xml"/>
  </target>

</project

此构建文件将找到您的所有 webapp 的 JSP 文件,将它们编译成 servlet 类,并为这些 JSP servlet 类生成 servlet 映射。它生成的 servlet 映射 ping 必须进入您的 webapp 的 WEB-INF/web.xml 文件,但是很难编写一个知道如何以可重复的方式将 servlet 映射插入到 web.xml 文件中的 Ant 构建文件构建文件运行的时间。相反,我们使用了 XML 实体包含,以便每次构建文件运行时生成的 servlet 映射进入一个新文件,并且该 servlet 映射文件可以通过 XML 实体包含机制插入到您的 web.xml 文件中。要使用它,您的 webapp 的 WEB-INF/web.xml 必须在文件顶部有一个特殊的实体声明,以及对 web.xml 文件内容中您希望 servlet 映射文件所在的实体的引用包括。以下是经过这些修改后空 servlet 2.5 webapp 的 web.xml 文件的外观:

<!DOCTYPE jspc-webxml [
    <!ENTITY jspc-webxml SYSTEM "jspc-web.xml">
  ]> 

  <web-app xmlns=http://java.sun.com/xml/ns/javaee
      xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance 
      xsi:schemaLocation="http:// java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/ 
  javaee/web-app_2_5.xsd"
      version="2.5"> 

    <!-- We include the JspC-generated mappings here. -->
    &jspc-webxml; 

    <!-- Non-generated web.xml content goes here. --> 

  </web-app> 

确保您的 web 应用程序的 web.xml 文件在文件顶部始终具有内联 DTD(DOCTYPE 标记),并在其下方具有 servlet 2.5 web-app 模式声明。然后,无论您想在 web.xml 文件中插入生成的 servlet 映射的什么位置,都将实体引用 &jspc-webxml; .请记住,实体引用以与号 ( & ) 开头,然后是实体的名称,并以分号 ( ; ) 结尾。

要使用构建文件,只需编辑它并将顶部的所有属性设置为与您的设置匹配的值,然后像这样运行它:

$ ant -f pre-compile-jsps.xml

【讨论】:

【参考方案2】:

如果您使用 duffymo 提到的解决方案,指向 Vinny Carpenter 的博客,我有一个提示。有一个区域导致我的容器在联系 localhost 时无限期挂起(特别是在私有 connect() 方法中)。使用以下 hack 是我的解决方法:

    private void connect(final String urlString) 

        HttpURLConnection conn;
        try 
            final URL url = new URL(urlString);
            conn = (HttpURLConnection)url.openConnection();
            conn.setConnectTimeout(5000);
            //time it out quickly - otherwise hangs forever
            //seems to be an issue hitting localhost
            //will still precompile the page
            conn.setReadTimeout(100);
            conn.setAllowUserInteraction(true);
            conn.getInputStream();
            conn.disconnect();
        
        catch (SocketTimeoutException e) 
            log.debug(e);
        
        catch (IOException ioe) 
            log.error(ioe);
        
    

设置超时并忽略 SocketTimeoutException 是有效的(尽管公认不是最好的解决方案)。此外,使用此过程意味着您需要在 web.xml 中指定 JSP。这足以满足我的需要。

【讨论】:

以上是关于如何让 Tomcat 在启动时预编译 JSP?的主要内容,如果未能解决你的问题,请参考以下文章

Eclipse 中启动tomcat后把jsp项目部署在什么位置

给tomcat配java版本

给tomcat配java版本

为啥我的 Tomcat 服务器在编译 JSP 时会抛出间歇性 404?

为啥在jsp更改时tomcat不需要重启

为啥在eclipse中修改JSP后tomcat自动重启?