0%

tomcat之JSP引擎

Tomcat JSP 引擎:Jasper 工作机制详解

在 Java Web 开发中,JSP(JavaServer Pages)是一种动态网页技术,它允许在 HTML 中嵌入 Java 代码。Tomcat 作为主流的 Servlet 容器,内置了专门的 JSP 引擎 ——Jasper,负责将 JSP 文件编译为 Servlet 类并执行。本文将详细解析 Jasper 的工作原理,包括运行时编译和预编译两种模式。

Jasper 引擎概述

Jasper 是 Tomcat 处理 JSP 的核心组件,其主要功能包括:

  • JSP 解析:将 JSP 文件中的 HTML 标签、JSP 指令(如 <%@ page %>)、脚本片段(如 <% ... %>)等解析为 Java 代码。
  • 编译:将解析后的 Java 代码编译为 Class 文件(Servlet 实现类)。
  • 执行:通过生成的 Servlet 类处理 HTTP 请求,生成动态响应。

Jasper 集成在 Tomcat 中,无需额外配置即可工作。其入口是 Tomcat 内置的 JspServlet,该 Servlet 在 conf/web.xml 中默认配置,用于处理所有以 .jsp.jspx 结尾的请求。

运行时编译:首次请求触发

Tomcat 并不会在 Web 应用启动时自动编译所有 JSP 文件,而是采用惰性编译策略 —— 仅在客户端第一次请求某个 JSP 页面时才触发编译。这一过程由 JspServlet 主导,具体流程如下:

1. JspServlet 配置

Tomcat 的 conf/web.xml 中默认配置了 JspServlet,负责拦截 JSP 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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> <!-- 启动时加载,优先级 3 -->
</servlet>

<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>

当客户端请求 http://localhost:8080/myapp/index.jsp 时,该请求会被 JspServlet 拦截处理。

2. 运行时编译流程

(1)确定 JSP 文件路径

JspServlet 根据请求的 URI 定位到 Web 应用中对应的 JSP 文件。例如,请求 /myapp/index.jsp 对应 Web 应用目录下的 index.jsp 文件。

(2)判断是否为预编译请求

如果请求参数中包含 jsp_precompile(如 index.jsp?jsp_precompile),则该请求为预编译请求—— 仅编译 JSP 文件,不执行其逻辑(不生成响应)。

注意:若使用 Spring MVC 等框架,请求先经过控制器再转发到 JSP 时,jsp_precompile 参数可能失效,因为转发过程会忽略该参数。

(3)创建 JspServletWrapper 实例

每个 JSP 页面对应一个 JspServletWrapper 实例,该类封装了 JSP 编译和执行的核心逻辑。其主要职责包括:

  • 检查 JSP 文件是否已编译,以及是否有更新(通过比较 JSP 文件和 Class 文件的修改时间)。
  • 若未编译或已更新,则触发编译流程。
  • 调用编译生成的 Servlet 类处理请求。
(4)JSP 编译为 Servlet

JspServletWrapper 通过 JspCompilationContext 完成编译,具体步骤:

  1. 解析 JSP:将 JSP 内容转换为 Java 代码(Servlet 源文件)。例如:
    • HTML 标签被转换为 out.write("<html>...</html>");
    • JSP 脚本 <% int i = 1; %> 直接嵌入 Java 代码。
    • JSP 指令 <%@ page contentType="text/html" %> 转换为 response.setContentType("text/html");
  2. 生成 Servlet 源文件:源文件默认存储在 $CATALINA_BASE/work/Catalina/localhost/<应用名>/org/apache/jsp/ 目录下,类名通常为 index_jsp(由 JSP 文件名生成)。
  3. 编译为 Class 文件:通过 Java 编译器(如 javac)将源文件编译为 Class 文件。Tomcat 可配置是否使用独立进程编译(fork 参数),以避免编译错误影响 Tomcat 主进程。
(5)执行 Servlet

编译完成后,JspServletWrapper 加载生成的 Servlet 类,调用其 service() 方法处理请求,生成动态响应。

3. 示例:JSP 到 Servlet 的转换

JSP 文件(index.jsp)

1
2
3
4
5
6
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<% out.println("Hello, JSP!"); %>
</body>
</html>

生成的 Servlet 核心代码(index_jsp.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase {
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
// 设置响应类型
response.setContentType("text/html;charset=UTF-8");
// 获取输出流
javax.servlet.jsp.JspWriter out = pageContext.getOut();
// 输出 HTML 和 Java 代码
out.write("<html>\n");
out.write(" <body>\n");
out.println("Hello, JSP!"); // 对应 JSP 脚本
out.write(" </body>\n");
out.write("</html>\n");
}
}

可以看到,JSP 最终被转换为一个继承 HttpJspBase(间接实现 Servlet 接口)的类,_jspService 方法对应请求处理逻辑。

预编译:提前编译所有 JSP

运行时编译虽能减少应用启动时间,但首次请求可能因编译耗时导致响应延迟。为此,Tomcat 提供预编译工具 JspC,支持在应用部署前批量编译所有 JSP 文件。

1. JspC 工具

JspC(JSP Compiler)是 Tomcat 提供的命令行工具,位于 bin 目录下(Windows 为 jspc.bat,Linux 为 jspc.sh)。其核心功能是扫描 Web 应用中的所有 JSP 文件,批量编译为 Servlet 类。

基本用法
1
2
# 编译 Web 应用 myapp 中的所有 JSP
jspc -webapp myapp -d build/classes -p org.apache.jsp
  • -webapp:指定 Web 应用目录。
  • -d:指定编译后的 Class 文件输出目录。
  • -p:指定生成的 Servlet 类的包名。

2. 预编译的优势

  • 减少首次请求延迟:提前完成编译,用户首次访问无需等待。
  • 部署前验证:提前发现 JSP 语法错误,避免部署后运行时异常。
  • 简化部署:可将编译后的 Class 文件直接打包,无需包含原始 JSP 文件,提高安全性。

3. 集成到构建流程

预编译通常集成到 Maven/Gradle 等构建工具中,例如使用 tomcat-jspc-maven-plugin 在打包时自动编译 JSP:

1
2
3
4
5
6
7
8
9
10
11
12
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat-jspc-maven-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>jspc</goal>
</goals>
</execution>
</executions>
</plugin>

Jasper 配置优化

通过调整 JspServlet 的初始化参数,可优化 Jasper 的性能和行为:

参数名 说明 默认值
fork 是否使用独立进程编译 JSP(true 可避免编译错误影响 Tomcat)。 false
development 是否开启开发模式(开启后会自动检测 JSP 修改并重新编译)。 true
checkInterval 开发模式下,检测 JSP 修改的间隔时间(秒),0 表示每次请求都检测。 0
keepgenerated 是否保留生成的 Java 源文件(便于调试)。 false

生产环境建议

  • 设置 development=false,关闭自动重编译,提高性能。
  • 设置 keepgenerated=false,避免泄露源码。

欢迎关注我的其它发布渠道