熟悉JAVA web开发的朋友都知道JSP会被转换成java文件(预编译),然后编译成class使用,即按照JSP-->java-->class的过程进行编译。
由于JVM只认识class文件,它不知道什么是JSP,因此在tomcat中 如何把JSP解析成java文件 就是本文所要描述的问题。
其他翻译内容参考:Tomcat官方文档翻译
如有错误,请予指正。
什么是Jasper
Jasper是tomcat中使用的JSP引擎,在Tomcat 6中使用的是Jasper 2,相对于原来的版本作了不少的改进,比如:JSP的标签缓冲池、后台编译、页面改变时自动重新编译、Eclipse中JDT编译等等。
那么Jasper到底是做什么的呢?
简单的说,就是把JVM不认识的JSP文件解析成java文件,然后编译成class文件提供使用。目前有很多的JSP解析引擎,Tomcat中使用的是Jasper。
在Tomcat中可以通过配置 CATALINA_HOME/conf/web.xml 中的内容,配置Jasper的选项(web.xml中的内容很长,截取其中的一部分):
<!-- The JSP page compiler and execution servlet, which is the mechanism --> <!-- used by Tomcat to support JSP pages. Traditionally, this servlet --> <!-- is mapped to the URL pattern "*.jsp". This servlet supports the --> <!-- following initialization parameters (default values are in square --> <!-- brackets): --> <!-- --> <!-- checkInterval If development is false and checkInterval is --> <!-- greater than zero, background compilations are --> <!-- enabled. checkInterval is the time in seconds --> <!-- between checks to see if a JSP page (and its --> <!-- dependent files) needs to be recompiled. [0] --> <!-- --> <!-- classdebuginfo Should the class file be compiled with --> <!-- debugging information? [true] --> <!-- --> <!-- classpath What class path should I use while compiling --> <!-- generated servlets? [Created dynamically --> <!-- based on the current web application] --> <!-- --> <!-- compiler Which compiler Ant should use to compile JSP --> <!-- pages. See the jasper documentation for more --> <!-- information. --> <!-- --> <!-- compilerSourceVM Compiler source VM. [1.5] --> <!-- --> <!-- compilerTargetVM Compiler target VM. [1.5] --> <!-- --> <!-- development Is Jasper used in development mode? If true, --> <!-- the frequency at which JSPs are checked for --> <!-- modification may be specified via the --> <!-- modificationTestInterval parameter. [true] --> <!-- --> <!-- displaySourceFragment --> <!-- Should a source fragment be included in --> <!-- exception messages? [true] --> <!-- --> <!-- dumpSmap Should the SMAP info for JSR45 debugging be --> <!-- dumped to a file? [false] --> <!-- False if suppressSmap is true --> <!-- --> <!-- enablePooling Determines whether tag handler pooling is --> <!-- enabled. This is a compilation option. It will --> <!-- not alter the behaviour of JSPs that have --> <!-- already been compiled. [true] --> <!-- --> <!-- engineOptionsClass Allows specifying the Options class used to --> <!-- configure Jasper. If not present, the default --> <!-- EmbeddedServletOptions will be used. --> <!-- --> <!-- errorOnUseBeanInvalidClassAttribute --> <!-- Should Jasper issue an error when the value of --> <!-- the class attribute in an useBean action is --> <!-- not a valid bean class? [true] --> <!-- --> <!-- fork Tell Ant to fork compiles of JSP pages so that --> <!-- a separate JVM is used for JSP page compiles --> <!-- from the one Tomcat is running in. [true] --> <!-- --> <!-- genStringAsCharArray --> <!-- Should text strings be generated as char --> <!-- arrays, to improve performance in some cases? --> <!-- [false] --> <!-- --> <!-- ieClassId The class-id value to be sent to Internet --> <!-- Explorer when using <jsp:plugin> tags. --> <!-- [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93] --> <!-- --> <!-- javaEncoding Java file encoding to use for generating java --> <!-- source files. [UTF8] --> <!-- --> <!-- keepgenerated Should we keep the generated Java source code --> <!-- for each page instead of deleting it? [true] --> <!-- --> <!-- mappedfile Should we generate static content with one --> <!-- print statement per input line, to ease --> <!-- debugging? [true] --> <!-- --> <!-- modificationTestInterval --> <!-- Causes a JSP (and its dependent files) to not --> <!-- be checked for modification during the --> <!-- specified time interval (in seconds) from the --> <!-- last time the JSP was checked for --> <!-- modification. A value of 0 will cause the JSP --> <!-- to be checked on every access. --> <!-- Used in development mode only. [4] --> <!-- --> <!-- recompileOnFail If a JSP compilation fails should the --> <!-- modificationTestInterval be ignored and the --> <!-- next access trigger a re-compilation attempt? --> <!-- Used in development mode only and is disabled --> <!-- by default as compilation may be expensive and --> <!-- could lead to excessive resource usage. --> <!-- [false] --> <!-- --> <!-- scratchdir What scratch directory should we use when --> <!-- compiling JSP pages? [default work directory --> <!-- for the current web application] --> <!-- --> <!-- suppressSmap Should the generation of SMAP info for JSR45 --> <!-- debugging be suppressed? [false] --> <!-- --> <!-- trimSpaces Should white spaces in template text between --> <!-- actions or directives be trimmed? [false] --> <!-- --> <!-- xpoweredBy Determines whether X-Powered-By response --> <!-- header is added by generated servlet [false] --> <!-- --> <!-- If you wish to use Jikes to compile JSP pages: --> <!-- Please see the "Using Jikes" section of the Jasper-HowTo --> <!-- page in the Tomcat documentation. --> <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>
具体的参数,上面都有解释,这里就不多赘述了。
都是些调节JSP编译的参数,比如多长时间检测一次,debug的调试信息相关配置,编译信息等等。
如何利用Japser.Jspc自定义预编译JSP
1 jasper相关jar包
在tomcat6中提供了几个jasper的jar包,相对于之前版本,去掉了jasper-compiler.jar以及jasper-runtime.jar,合并为jasper.jar
另外如果开发者自己想要编译JSP,还需要使用ant以及tomcat-juli.jar。
2 ant相关jar包
其中ant相关的jar包可以去官网下载ant.zip,然后解压提取其中lib内的jar包。
3 tomcat-juli相关jar包
tomcat-juli.jar位于CATALINA_HOME/bin/目录下。
在Eclipse的构建路径下添加上述相关的jar包即可,然后创建测试类:
添加JAR包步骤:右键工程-->Properties-->Java Build Path-->Libraries-->Add External JARs-->选择添加的JAR包-->OK
package com.test; import org.apache.jasper.JspC; public class testCompiler{ public String jspcTest(){ String error=""; try { JspC jspc = new JspC(); //第一种方式 String[] arg0 = {"-uriroot", "F:/apache-tomcat-6.0.43/webapps/ROOT", "-d", "F:/test", "index.jsp" }; jspc.setArgs(arg0); //第二种方式 /*jspc.setUriroot("F:/apache-tomcat-6.0.43/webapps/ROOT");//web应用的root目录 jspc.setOutputDir("F:/test");//.java文件和.class文件的输出目录 jspc.setJspFiles("index.jsp");//要编译的jsp */ jspc.setCompile(true);//是否编译 false或不指定的话只生成.java文件 jspc.execute(); }catch(Exception e){ error=e.toString(); } return error; } public static void main(String args[]){ testCompiler t=new testCompiler(); System.out.println(t.jspcTest()); } }
可以使用两种方式进行自定义的JSP编译。
测试后,可以在 F:/test 目录下发现编译出的index.jsp的java文件以及class文件。
参考
【1】Jasper2 JSP引擎:http://tomcat.apache.org/tomcat-6.0-doc/jasper-howto.html
【2】解读JSP解析过程:http://www.cnblogs.com/zollty/p/3309310.html
【3】使用Jspc编译JSP文件:http://kjah.iteye.com/blog/625588
- JSP Custom Tag Pooling - 为JSP自定义标记实例化的java对象现在可以被池化和重用。这极大地提高了使用自定义标记的JSP页面的性能。
- Background JSP compilation - 如果您对已经编译过的JSP页面进行了更改,那么Jasper 2可以在后台重新编译该页面。以前编译的JSP页面仍然可用来服务请求。一旦新页面被成功编译,它将取代旧页面。这有助于提高生产服务器上JSP页面的可用性。
- Recompile JSP when included page changes - Jasper 2现在可以检测到,从JSP中包含的一个页面已经改变了,然后重新编译了父JSP。
- JDT used to compile JSP pages - 现在,Eclipse JDT Java编译器被用于执行JSP Java源代码编译。这个编译器从容器类加载器加载源依赖项。Ant和javac仍然可以使用。
- /**
- * Copyright © 2017 http://blog.csdn.net/noseparte © Like the wind, like rain
- * @Author Noseparte
- * @Compile 2017年12月7日 -- 下午4:34:36
- * @Version 1.0
- * @Description JspServlet源码分析
- */
- public class JspServlet extends HttpServlet implements PeriodicEventListener {
- /**
- * @see transient修饰词释义
- * java语言的关键字,变量修饰符,如果用transient声明一个实例变量,
- * 当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。
- */
- private static final long serialVersionUID = 1L;
- private final transient Log log = LogFactory.getLog(JspServlet.class);
- private transient ServletContext context; //ServletContext对象包含在ServletConfig对象中,当servlet被初始化时,Web服务器提供servlet。
- private ServletConfig config; //servlet容器使用的servlet配置对象在初始化期间将信息传递给servlet。
- private transient Options options; //一个类来保存所有与JSP引擎相关的init参数。
- private transient JspRuntimeContext rctxt; //用于跟踪JSP编译时文件依赖项的类
- private String jspFile;
- /**
- * javax.servlet.GenericServlet由servlet容器调用,以指示servlet正在被放置到服务中。
- * 看到Servlet.init(javax.servlet.ServletConfig)。
- * 该实现存储从servlet容器接收的ServletConfig对象,以供以后使用。
- * 当覆盖该方法的这种形式时,调用super.init(config)。
- * @param config
- */
- public void init(ServletConfig config) throws ServletException {
- super.init(config);
- this.config = config;
- this.context = config.getServletContext();
- String engineOptionsName = config.getInitParameter("engineOptionsClass");
- if ((Constants.IS_SECURITY_ENABLED) && (engineOptionsName != null)) {
- this.log.info(Localizer.getMessage("jsp.info.ignoreSetting",
- "engineOptionsClass", engineOptionsName));
- engineOptionsName = null;
- }
- if (engineOptionsName != null) {
- try {
- ClassLoader loader = Thread.currentThread()
- .getContextClassLoader();
- Class<?> engineOptionsClass = loader.loadClass(engineOptionsName);
- Class<?>[] ctorSig = { ServletConfig.class, ServletContext.class };
- Constructor<?> ctor = engineOptionsClass.getConstructor(ctorSig);
- Object[] args = { config, this.context };
- this.options = ((Options) ctor.newInstance(args));
- } catch (Throwable e) {
- e = ExceptionUtils.unwrapInvocationTargetException(e);
- ExceptionUtils.handleThrowable(e);
- this.log.warn("Failed to load engineOptionsClass", e);
- this.options = new EmbeddedServletOptions(config, this.context);
- }
- } else {
- this.options = new EmbeddedServletOptions(config, this.context);
- }
- this.rctxt = new JspRuntimeContext(this.context, this.options);
- if (config.getInitParameter("jspFile") != null) {
- this.jspFile = config.getInitParameter("jspFile");
- try {
- if (null == this.context.getResource(this.jspFile)) {
- return;
- }
- } catch (MalformedURLException e) {
- throw new ServletException("cannot locate jsp file", e);
- }
- try {
- if (SecurityUtil.isPackageProtectionEnabled()) {
- AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
- public Object run()
- throws IOException, ServletException {
- JspServlet.this.serviceJspFile(null, null,
- JspServlet.this.jspFile, true);
- return null;
- }
- });
- } else {
- serviceJspFile(null, null, this.jspFile, true);
- }
- } catch (IOException e) {
- throw new ServletException("Could not precompile jsp: " +
- this.jspFile, e);
- } catch (PrivilegedActionException e) {
- Throwable t = e.getCause();
- if ((t instanceof ServletException)) {
- throw ((ServletException) t);
- }
- throw new ServletException("Could not precompile jsp: " +
- this.jspFile, e);
- }
- }
- if (this.log.isDebugEnabled()) {
- this.log.debug(Localizer.getMessage("jsp.message.scratch.dir.is",
- this.options.getScratchDir().toString()));
- this.log.debug(Localizer.getMessage(
- "jsp.message.dont.modify.servlets"));
- }
- }
- /**
- * 返回JspServletWrappers存在的jsp数量
- * @return
- */
- public int getJspCount() {
- return this.rctxt.getJspCount();
- }
- /**
- * 重新设置JSP重新加载计数器。
- * @param count
- */
- public void setJspReloadCount(int count) {
- this.rctxt.setJspReloadCount(count);
- }
- /**
- * 获取已重新加载的jsp的数量。
- * @return
- */
- public int getJspReloadCount() {
- return this.rctxt.getJspReloadCount();
- }
- /**
- * 获得JSP限制队列中JSP的数量
- * @return
- */
- public int getJspQueueLength() {
- return this.rctxt.getJspQueueLength();
- }
- /**
- * 获得已卸载的jsp的数量。
- * @return
- */
- public int getJspUnloadCount() {
- return this.rctxt.getJspUnloadCount();
- }
- boolean preCompile(HttpServletRequest request) throws ServletException {
- String queryString = request.getQueryString();
- if (queryString == null) {
- return false;
- }
- int start = queryString.indexOf(Constants.PRECOMPILE);
- if (start < 0) {
- return false;
- }
- queryString = queryString.substring(start +
- Constants.PRECOMPILE.length());
- if (queryString.length() == 0) {
- return true;
- }
- if (queryString.startsWith("&")) {
- return true;
- }
- if (!queryString.startsWith("=")) {
- return false;
- }
- int limit = queryString.length();
- int ampersand = queryString.indexOf('&');
- if (ampersand > 0) {
- limit = ampersand;
- }
- String value = queryString.substring(1, limit);
- if (value.equals("true")) {
- return true;
- }
- if (value.equals("false")) {
- return true;
- }
- throw new ServletException("Cannot have request parameter " +
- Constants.PRECOMPILE + " set to " + value);
- }
- /**
- * javax.servlet.http.HttpServlet从公共服务方法接收标准HTTP请求,并将它们分派到这个类中定义的doMethod方法。
- * 该方法是servlet.service(javax.servlet的一个特定于http的版本。
- * ServletRequest,javax.servlet.ServletResponse)方法。没有必要覆盖这个方法。
- */
- public void service(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- String jspUri = this.jspFile;
- if (jspUri == null) {
- jspUri = (String) request.getAttribute(
- "javax.servlet.include.servlet_path");
- if (jspUri != null) {
- String pathInfo = (String) request.getAttribute(
- "javax.servlet.include.path_info");
- if (pathInfo != null) {
- jspUri = jspUri + pathInfo;
- }
- } else {
- jspUri = request.getServletPath();
- String pathInfo = request.getPathInfo();
- if (pathInfo != null) {
- jspUri = jspUri + pathInfo;
- }
- }
- }
- if (this.log.isDebugEnabled()) {
- this.log.debug("JspEngine --> " + jspUri);
- this.log.debug("\t ServletPath: " + request.getServletPath());
- this.log.debug("\t PathInfo: " + request.getPathInfo());
- this.log.debug("\t RealPath: " +
- this.context.getRealPath(jspUri));
- this.log.debug("\t RequestURI: " + request.getRequestURI());
- this.log.debug("\t QueryString: " + request.getQueryString());
- }
- try {
- boolean precompile = preCompile(request);
- serviceJspFile(request, response, jspUri, precompile);
- } catch (RuntimeException e) {
- throw e;
- } catch (ServletException e) {
- throw e;
- } catch (IOException e) {
- throw e;
- } catch (Throwable e) {
- ExceptionUtils.handleThrowable(e);
- throw new ServletException(e);
- }
- }
- /**
- * javax.servlet.GenericServlet由servlet容器调用,
- * 以指示servlet正在被从服务中取出/销毁
- */
- public void destroy() {
- if (this.log.isDebugEnabled()) {
- this.log.debug("JspServlet.destroy()");
- }
- this.rctxt.destroy();
- }
- /**
- * 周期性的eventlistener,执行一个周期性任务,比如重新加载,等等。
- */
- public void periodicEvent() {
- this.rctxt.checkUnload();
- this.rctxt.checkCompile();
- }
- private void serviceJspFile(HttpServletRequest request,
- HttpServletResponse response, String jspUri, boolean precompile)
- throws ServletException, IOException {
- JspServletWrapper wrapper = this.rctxt.getWrapper(jspUri);
- if (wrapper == null) {
- synchronized (this) {
- wrapper = this.rctxt.getWrapper(jspUri);
- if (wrapper == null) {
- if (null == this.context.getResource(jspUri)) {
- handleMissingResource(request, response, jspUri);
- return;
- }
- wrapper = new JspServletWrapper(this.config, this.options,
- jspUri, this.rctxt);
- this.rctxt.addWrapper(jspUri, wrapper);
- }
- }
- }
- try {
- wrapper.service(request, response, precompile);
- } catch (FileNotFoundException fnfe) {
- handleMissingResource(request, response, jspUri);
- }
- }
- private void handleMissingResource(HttpServletRequest request,
- HttpServletResponse response, String jspUri)
- throws ServletException, IOException {
- String includeRequestUri = (String) request.getAttribute(
- "javax.servlet.include.request_uri");
- if (includeRequestUri != null) {
- String msg = Localizer.getMessage("jsp.error.file.not.found", jspUri);
- throw new ServletException(SecurityUtil.filter(msg));
- }
- try {
- response.sendError(404, request.getRequestURI());
- } catch (IllegalStateException ise) {
- this.log.error(Localizer.getMessage("jsp.error.file.not.found",
- jspUri));
- }
- }
- }
- checkInterval - 如果开发是false,而checkInterval大于零,则启用后台编译。checkInterval是在检查之间的几秒之间的时间,以查看是否需要重新编译JSP页面(及其依赖的文件)。默认值0秒。
- classdebuginfo - 应该使用调试信息编译类文件吗?真或假,默认为真。
- classpath - 定义用于编译生成的servlet的类路径。该参数如果产生影响只有org.apache.jasper.Constants.SERVLET_CLASSPATH不设置。当Jasper在Tomcat中使用时,这个属性总是被设置的。默认情况下,类路径是根据当前的web应用程序动态创建的。
- compiler - 应该使用哪个编译器来编译JSP页面。此值的有效值与Ant的javac任务的编译器属性相同。如果没有设置值,那么将使用默认的Eclipse JDT Java编译器,而不是使用Ant。没有默认值。如果该属性设置为setenv。应该使用sh bat来添加ant.jar,ant-launcher jar。jar和工具。jar到CLASSPATH环境变量。
- compilerSourceVM -什么JDK版本是与之兼容的? (Default value: 1.7)
- compilerTargetVM - 生成的文件与什么JDK版本兼容? (Default value: 1.7)
- development - Jasper在发展模式中使用了吗?如果是正确的,可以通过变更测试间隔参数指定对jsp进行修改的频率。真或假,默认为真。
- displaySourceFragment - 是否应该将一个源片段包含在异常消息中?真或假,默认为真。
- dumpSmap - 是否应该将JSR45调试的SMAP信息转储到文件中?真或假,默认为假。如果抑制映射为真,则是假的。
- enablePooling - 确定是否启用了标记处理程序池。这是一个编译选项。它不会改变已经编译的jsp的行为。真或假,默认为真。
- engineOptionsClass - 允许指定用于配置Jasper的选项类。如果不存在,将使用默认的嵌入服务。如果在SecurityManager下运行该选项会被忽略.
- errorOnUseBeanInvalidClassAttribute - 当一个使用bean操作中的类属性值不是一个有效的bean类时,贾斯珀是否会发出错误?真或假,默认为真。
- fork - 是否有Ant fork JSP页面编译,以便在与Tomcat不同的JVM中执行它们?真或假,默认为真。
- genStringAsCharArray - 是否应该将文本字符串作为char数组生成,以提高某些情况下的性能?默认的错误。
- ieClassId - 当使用了标签时,类id值将被发送给Internet Explorer。默认clsid:8 ad9c840 - 044 e - 11 - d1 b3e9 f499d93——00805。
- javaEncoding - 用于生成Java源文件的Java文件编码。use UTF8违约。
- keepgenerated - 我们应该为每个页面保留生成的Java源代码,而不是删除它吗?真或假,默认为真。
- mappedfile - 我们是否应该在每个输入行中生成静态内容,以简化调试工作?真或假,默认为真。
- maxLoadedJsps - 将为web应用程序加载的jsp的最大数量。如果加载的jsp数量超过了这一数量,那么最近使用的jsp将会被卸载,以便在任何时候加载的jsp的数量不会超过这个限制。0或更少的值表示没有限制。默认为-1
- jspIdleTimeout - 在它被卸载之前,JSP可以在秒内空闲的时间。0或更少的值表示永远不会卸载。默认为 -1
- modificationTestInterval - 在指定的时间间隔(以秒为单位)从上一次检查JSP进行修改时,导致JSP(及其相关文件)在指定的时间间隔内不被检查。值为0将导致在每次访问时对JSP进行检查。仅用于开发模式。默认为4秒。
- recompileOnFail - 如果一个JSP编译失败了,是否应该忽略修改后的测试间隔,而下一个访问将触发重新编译的尝试?只在开发模式中使用,默认情况下是禁用的,因为编译可能很昂贵,可能导致资源的过度使用。
- scratchdir - 在编译JSP页面时,我们应该使用什么scratch目录?默认是当前web应用程序的工作目录。如果在SecurityManager下运行,这个选项会被忽略。
- suppressSmap - 对于jsr 45调试的SMAP信息是否应该被抑制?真或假,默认为假。
- trimSpaces - 应该删除完全由空格组成的模板文本吗?真或假,默认为假。
- xpoweredBy - 确定由生成的servlet添加的x-power-响应头是否被添加。真或假,默认为假。
- strictQuoteEscaping - 当scriptlet表达式用于属性值时,是否应该严格地应用JSP.1.6中的规则来转义引号中的字符?真或假,默认为真。
- quoteAttributeEL - 当在JSP页面的属性值中使用EL时,是否应该将JSP.1.6中所描述的属性引用的规则应用到表达式中?真或假,默认为真。
- 减小JSP的大小
- 通过将抑制映射设置为true,禁用SMAP生成和JSR-045支持。
- development - 要禁用JSP页面编译的访问检查,将此设置为false。
- genStringAsCharArray - 为了生成更高效的char数组,请将其设置为true。
- modificationTestInterval - 如果开发必须以任何理由(如jsp的动态生成)被设置为true,那么将其设置为高值将会极大地提高性能。
- trimSpaces - 为了从响应中删除无用的字节,将其设置为true。
- 当您切换到另一个Tomcat版本时,使用新的Tomcat版本重新生成并重新编译您的jsp。
- 使用java系统属性在服务器运行时禁用PageContext池org.apache.jasper.runtime.JspFactoryImpl.USE_POOL = false。和限制缓冲org.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER = true。请注意,更改默认值可能会影响性能,但它将根据应用程序的不同而有所不同。