Weblogic环境下的JSP预编译
1、引言
JSP的延迟编译特性确实给性能带来了不大不小的影响,第一次访问超慢的那种状况给人的感觉非常不好,于是花了点时间研究了下JSP预编译,下面就说说怎么个预编译法。
1.1、预编译的方法论
根据研究的结果,发现要实现JSP预编译这一目的,有不同的方法或者途径,简介如下:
1)、人工来执行一个应用程序,说白了就是发动人去挨个点页面,这是最简单的方法,但也是个体力活,缺点不用讲,是显而易见的,不到万不得已肯定不能采取此种方法,碰到个大应用,动辄几百上千jsp人都会疯掉!
2)、使用命令行JAVA工具weblogic.jspc进行程序式预编译;
3)、在服务器启动部署一个特定的WEB应用程序时进行预编译(声明式预编译);
下面主要对第二和第三种做下介绍,先说第三种。
1.1.1、声明式预编译
对于在WLS下声明的预编译,一个特定的Web应用程序(独立的或者作为EAR的一部分)能够被配置,因此所有的JSP在应用程序部署(服务器启动时)和重新部署(运行时)期间里被预编译。对WEB-INF/ weblogic.xml部署描述符要做必要的配置变化,使用预编译<jsp-param/>指令,如下:
<weblogic-web-app>
…
<jsp-descriptor>
<jsp-param>
<param-name>precompile</param-name>
<param-value>true</param-value>
</jsp-param>
</jsp-descriptor>
…
</weblogic-web-app>
在一个特定的Web应用程序上进行部署(或重新部署),如果上述的参数被设定成真, WLS 将会在WAR内尝试预编译所有的JSP文件,在程序中从 Web 应用程序的根目录下循环的运行它的方法( 略过Web-INF) 。以. jsp 或 .JSP为扩展名的文件都变成了编译的对象。 被编译后的文件被以适当的包目录结构形式被放置在Web 应用程序的临时工作目录下面(默认在Web-INF子目录中,除非在 weblogic.xml 里有特别说明)。
这个方法是到目前为止进行JSP预编译最方便的途径(“flick-a-switch” 途径),他有许多指出来毫无意义的缺点。如果一个错误在JSP的编译期间或在部署(或重新部署) 的时候发生,Web 应用程序的预编译将会在例外处暂停。另外,如果在一个特定的Web应用程序里面有许多JSP文件的情况,declarative预编译显著的影响着部署时间,阻断部署直到所有的文件都被编译。对于大型的应用程序,当出现数以百计的JSP 文件以declarative预编译被执行的时候,这种部署时间趋向以分钟来计算 (在某些情况10到15分钟,其他情况可能更长时间)。设想开始一个服务器实例,在一个特定的Web应用程序周期内进入部署状态用declarative 预编译激活。如果在应用内有很多的JSP文件以及部署,接近完成时就已经花费了大量的时间,在编译期间由于抛出一个例外而突然失败,当然会引起挫折感。虽然起先看起来比较方便,但declarative 编译对生产系统管理造成重大的风险,因此应该在经过慎重的考虑后再使用它。
1.1.2、程序式预编译
为了方便测试,在Eclipse中新建一个最简单的Web工程,只有一个默认的index.jsp文件:
Weblogic提供了weblogic.jspc用以编译jsp文件,这里我们使用ant来实现这一目的。在程序根目录新建一个build.xml文件,内容如下:
<project name="TestPreCompile" default="precompile">
<property name="dist.dir" value="dist"/>
<property name="web.dir" value="WebRoot"/>
<property name="weblogic.domain" value="D:/Softs/java/bea/user_projects/domains/testdomain/autodeploy"/>
<path id="classpath">
<fileset dir="D:/Softs/java/bea/wlserver_10.0/server/lib">
<include name="*.jar"/>
</fileset>
<fileset dir="D:/Softs/java/bea/jdk150_06/lib">
<include name="*.jar"/>
</fileset>
</path>
<!-- =================================
target: precompile
================================= -->
<target name="precompile" description="--> description">
<java classname="weblogic.jspc" classpathref="classpath" fork="true" failonerror="yes" maxmemory="1028m">
<arg line="-webapp d:/TestPlace/TestPreCompile/ -d d:/TestPlace/TestPreCompile/WebRoot/WEB-INF/classes -compileAll"/>
</java>
</target>
<!-- =================================
target: war
================================= -->
<target name="war" depends="clean,precompile" description="--> description">
<mkdir dir="${dist.dir}"/>
<war destfile="${dist.dir}/TestPreCompile.war" webxml="${web.dir}/WEB-INF/web.xml">
<classes dir="${web.dir}/WEB-INF/classes"></classes>
<fileset dir="${web.dir}">
<include name="**/weblogic.xml"/>
</fileset>
</war>
</target>
<!-- =================================
target: clean
================================= -->
<target name="clean" description="--> description">
<delete dir="${dist.dir}"></delete>
</target>
<!-- =================================
target: deploy_war
================================= -->
<target name="deploy_war" depends="war" description="--> description">
<copy todir="${weblogic.domain}" preservelastmodified="true">
<fileset dir="${dist.dir}">
<include name="*.war"/>
</fileset>
</copy>
</target>
</project>
针对这个ant脚本,我来解释下:
1. Clean任务负责情况上次的编译结果;deploy_war任务负责将编译好的war包发布到weblogic;precompile是核心任务,负责编译项目中的jsp文件为servlet;war任务负责打war包。如下:
2. 在precompile任务中,调用了weblogic.jspc,该类位于weblogic.jar包内,因此要将此包放入classpath内,这就是classpathref的作用,fork是叉牛排用的大叉子,因其一个把手多个爪子的特色,故而被借用到计算机中来代称“指令的派生”,一生二,二生三,三生无穷。我们知道ant本身必须跑在虚拟机环境之中,而每个任务也同样必须跑在一个虚拟机环境之中。这样就存在一个选择的问题:任务可以和ant处在同一虚拟机中,也可以不。fork正是来干这件事的,它来负责做决定。Maxmemory属性要指定,不然文件多了就会失败。Arg标签指定了weblogic.jspc的一些参数,这里是比较关键的几个参数,-webapp表示web应用所在的目录,编译程序会在此目录及其子目录寻找jsp文件,-d参数指示jsp编译后的class文件放哪里,一般是web目录下的WEB-INF/classes目录,-compileAll表示编译-d指定的目录及其所有子目录。
3. 修改web.xml文件,将jsp的解析器改为JSPClassServlet,它将不对jsp文件进行时间戳校对,所以每次更新了jsp文件都需要重新执行本预编译程序。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
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">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>JSPClassServlet</servlet-name>
<servlet-class>weblogic.servlet.JSPClassServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>JSPClassServlet</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
</web-app>
4. 新建weblogic.xml文件,由于我们已经使用了jspc进行了预编译,为了防止weblogic再次进行编译,因此我们要关掉它的自动预编译功能。内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web Application 7.0//EN"
"http://www.bea.com/servers/wls700/dtd/weblogic700-web-jar.dtd">
<weblogic-web-app>
<jsp-descriptor>
<jsp-param>
<param-name>precompile</param-name>
<param-value>false</param-value>
</jsp-param>
<jsp-param>
<param-name>pageCheckSeconds</param-name>
<param-value>-1</param-value>
</jsp-param>
</jsp-descriptor>
</weblogic-web-app>
5. 然后,通过precompile任务编译,下面是编译后的目标目录生成的class文件,它是一个servlet:
6. 使用war任务打包成war包后,部署到weblogic的domain内。
7. 访问index.jsp文件,注意这里的地址是:http://localhost:7001/TestPreCompile/WebRoot/index.jsp
1.2、参考
附:JSPc 编译器选项
使用以下选项的任何组合:
-classpath
添加组成所需 CLASSPATH 的目录的列表(在 Windows NT/2000 平台上由分号分隔,在 UNIX 平台上由冒号分隔)。包括包含 JSP 所需的任何类的目录。例如(要在一行上输入):
$ java weblogic.jspc -classpath java/classes.zip;/weblogic/classes.zip myFile.JSP
-charsetMap
指定 JSP contentType 指令中使用的 IANA 或非正式字符集名称到 Java 字符集名称的映射。例如: -charsetMap x-sjis=Shift_JIS,x-big5=Big5
最常用的映射已内置到 JSP 编译器中。仅在未识别出所需的字符集映射时,才使用此选项。
-commentary
使 JSP 编译器将来自 JSP 的注释包含在生成的 HTML 页中。如果忽略此选项,则注释不会出现在生成的 HTML 页中。
-compileAll
递归编译当前目录中或通过 -webapp 标志指定的目录中的所有 JSP。(请参阅此选项列表中的 -webapp 条目)。还会编译子目录中的 JSP。
-compileFlags
将一个或多个命令行标志传递到编译器。将多个标志括在引号中,以空格分隔。例如: java weblogic.jspc -compileFlags "-g -v" myFile.jsp
-compiler
指定要用于从生成的 Java 源代码编译类文件的 Java 编译器。使用的默认编译器为 javac。除非您显式指定编译器的绝对路径,否则 Java 编译器程序应位于您的 PATH 中。
-compilerclass
将 Java 编译器作为 Java 类(而不是本地可执行文件)运行。
-d <dir>
指定已编译输出(即类文件)的目标。将此选项用作将已编译的类放入已位于 CLASSPATH 中的目录的快捷方式。
-depend
如果以前为某个 JSP 生成的类文件具有比该 JSP 源文件更新的日期戳,则不会重新编译该 JSP。
-debug
在启用调试的情况下进行编译。
-deprecation
将源文件编译为类文件时,如果在生成的 Java 源文件中使用了不赞成使用的方法,则会对此做出警告。
-docroot directory
请参阅 -webapp。
-encoding default|named character encoding
有效参数包括 (a) default,它指定使用 JDK 默认字符编码,(b) 指定的字符编码,如 8859_1。如果未指定 -encoding 标志,则使用字节数组。
-g
指示 Java 编译器将调试信息包含在类文件中。
-help
显示 JSP 编译器的所有可用标志的列表。
-J
获取传递到您的编译器的选项的列表。
-k
当使用单个命令编译多个 JSP 时,即使无法编译这些 JSP 中的一个或多个 JSP,编译器也会继续进行编译。
-keepgenerated
保留编译过程中作为中间级创建的 Java 源代码文件。通常情况下,这些文件会在编译之后删除。
-noTryBlocks
如果 JSP 文件具有许多自定义 JSP 标记或嵌套很深的自定义 JSP 标记,并且您在编译时收到 java.lang.VerifyError 异常,则使用此标志可使 JSP 正确进行编译。
-nowarn
从 Java 编译器关闭警告消息。
-noPrintNulls
在 JSP 表达式中将“null”显示为“”。
-O
在打开优化的情况下编译 Java 源文件。此选项会替换 -g 标志。
-package packageName
设置预规划给生成的 Java HTTP Servlet 的包名的包名。默认为 jsp_servlet。
-superclass classname
设置由生成的 Servlet 扩展的超类的类名。命名的超类必须是 HttpServlet 或 GenericServlet 的派生。
-verbose
将 verbose 标志传递到使用 compiler 标志指定的 Java 编译器。有关详细信息,请参阅编译器文档。默认值为 Off。
-verboseJavac
输出由指定的 JSP 编译器生成的消息。
-version
输出 JSP 编译器的版本。
-webapp directory
展开的目录格式的包含 Web 应用程序的目录名称。如果 JSP 包含对 Web 应用程序中的资源(如 JSP 标记库或其他 Java 类)的引用,则 JSP 编译器将在此目录中查找这些资源。如果在编译需要 Web 应用程序中的资源的 JSP 时省略此标志,则编译将失败。