# 利刃出鞘_Tomcat 核心原理解析(四)

# 利刃出鞘_Tomcat 核心原理解析(四)

经验文章nimo972024-12-23 9:45:5710A+A-

# 利刃出鞘_Tomcat 核心原理解析(四)

## 一、Tomcat专题 - Jasper 引擎 - 介绍

### 1、JSP 脚本。

1)对于基于 JSP 的 web 应用来说,我们可以直接在 JSP 页面中编写 Java 代码,添加第三方的标签库,以及使用 EL 表达式。但是无论经过何种形式的处理,最终输出到客户端的都是标准的 HTML 页面(包含js ,css...),并不包含任何的 java 相关的语法。

2)也就是说, 我们可以把 jsp 看做是一种运行在服务端的脚本。

### 2、Jasper 简介

那么服务器是如何将 JSP 页面转换为 HTML 页面的呢?

Jasper 模块是 Tomcat 的 JSP 核心引擎,我们知道 JSP 本质上是一个 Servlet。Tomcat 使用 Jasper 对 JSP 语法进行解析,生成 Servlet 并生成 Class 字节码,用户在进行访问 jsp 时,会访问 Servlet,最终将访问的结果直接响应在浏览器端 。另外,在运行的时候,Jasper 还会检测 JSP 文件是否修改,如果修改,则会重新编译 JSP 文件。

## 二、Tomcat专题 - Jasper 引擎 - 编译方式 - 流程源码

### 1、 JSP 编译方式:运行时编译

Tomcat 并不会在启动 Web 应用的时候自动编译 JSP 文件, 而是在客户端第一次请求时,才编译需要访问的 JSP 文件。

### 2、创建一个 web 项目, 并编写 JSP 代码 :

```java

<%@ page import="java.text.DateFormat" %>

<%@ page import="java.text.SimpleDateFormat" %>

<%@ page import="java.util.Date" %>

<%@ page contentType="text/html;charset=UTF‐8" language="java" %>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>

<head>

<title>$Titlelt;/title>

</head>

<body>

<%

DateFormat dateFormat = new SimpleDateFormat("yyyy‐MM‐dd

HH:mm:ss");

String format = dateFormat.format(new Date());

%>

Hello , Java Server Page 。。。。

<br/>

<%= format %>

</body>

</html>

```

### 3、编译过程

Tomcat 在默认的 web.xml 中配置了一个 org.apache.jasper.servlet.JspServlet,用于处

理所有的.jsp 或 .jspx 结尾的请求,该 Servlet 实现即是运行时编译的入口。

```java

<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>

<servlet-mapping>

<servlet-name>jsp</servlet-name>

<url-pattern>*.jsp</usr-pattern>

<url-pattern>*jspx</url-pattern>

</servlet-mapping>

```

### 4、JspServlet 处理流程图:

### 5、编译结果

1) 如果在 tomcat/conf/web.xml 中配置了参数 scratchdir , 则jsp编译后的结果,就会存储在该目录下 。

```java

<init-param>

<param-name>scratchdir</param-name>

<param-value>d:/tmp/jsp/</param-value>

</init-param>

```

2) 如果没有配置该选项, 则会将编译后的结果,存储在 Tomcat 安装目录下的

work/Catalina(Engine名称)/localhost(Host名称)/Context 名称 。

假设项目名称为 jsp_demo 01。

3) 如果使用的是 IDEA 开发工具集成 Tomcat 访问 web 工程中的 jsp ,编译后的结果,存放在 :

```java

C:\Users\Administrator\.IntelliJIdea2019.1\system\tomcat\_project_tomcat\work\Catalina\localhost\jsp_demo_01_war_exploded\org\apache\jsp

```

### 6、预编译

- 除了运行时编译,我们还可以直接在 Web 应用启动时,一次性将 Web 应用中的所有的 JSP 页面一次性编译完成。在这种情况下,Web 应用运行过程中,便可以不必再进行实时编译,而是直接调用 JSP 页面对应的 Servlet 完成请求处理, 从而提升系统性能。

- Tomcat 提供了一个 Shell 程序 JspC,用于支持 JSP 预编译,而且在 Tomcat 的安装目录下提供了一个 catalina-tasks.xml 文件声明了 Tomcat 支持的 Ant 任务, 因此,我们很容易使用 Ant 来执行 JSP 预编译 。(要想使用这种方式,必须得确保在此之前已经下载并安装了Apache Ant)。

## 三、Tomcat专题 - Jasper 引擎 - 编译原理

### 1、编译原理:代码分析

编译后的.class 字节码文件及源码 :

```java

public final class index_jsp extends

org.apache.jasper.runtime.HttpJspBase

implements

org.apache.jasper.runtime.JspSourceDependent,org.apache.jasper.runtime.Js

pSourceImports {

private static final javax.servlet.jsp.JspFactory _jspxFactory =

javax.servlet.jsp.JspFactory.getDefaultFactory();

private static java.util.Map<java.lang.String,java.lang.Long>

_jspx_dependants;

static {

_jspx_dependants = new

java.util.HashMap<java.lang.String,java.lang.Long>(2);

_jspx_dependants.put("jar:file:/D:/DevelopProgramFile/apache‐tomcat‐

8.5.42‐windows‐x64/apache‐tomcat‐8.5.42/webapps/jsp_demo_01/WEB‐

INF/lib/standard.jar!/META‐INF/c.tld", Long.valueOf(1098682290000L));

_jspx_dependants.put("/WEB‐INF/lib/standard.jar",

Long.valueOf(1490343635913L));

}

private static final java.util.Set<java.lang.String>

_jspx_imports_packages;

private static final java.util.Set<java.lang.String>

_jspx_imports_classes;

static {

_jspx_imports_packages = new java.util.HashSet<>();

_jspx_imports_packages.add("javax.servlet");

_jspx_imports_packages.add("javax.servlet.http");

_jspx_imports_packages.add("javax.servlet.jsp");

_jspx_imports_classes = new java.util.HashSet<>();

_jspx_imports_classes.add("java.util.Date");

_jspx_imports_classes.add("java.text.SimpleDateFormat");

_jspx_imports_classes.add("java.text.DateFormat");

}

private volatile javax.el.ExpressionFactory _el_expressionfactory;

private volatile org.apache.tomcat.InstanceManager

_jsp_instancemanager;

public java.util.Map<java.lang.String,java.lang.Long> getDependants() {

return _jspx_dependants;

}

public java.util.Set<java.lang.String> getPackageImports() {

return _jspx_imports_packages;

}

public java.util.Set<java.lang.String> getClassImports() {

return _jspx_imports_classes;

}

public javax.el.ExpressionFactory _jsp_getExpressionFactory() {

if (_el_expressionfactory == null) {

synchronized (this) {

if (_el_expressionfactory == null) {

_el_expressionfactory =

_jspxFactory.getJspApplicationContext(getServletConfig().getServletContex

t()).getExpressionFactory();

}

}

}

return _el_expressionfactory;

}

public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {

if (_jsp_instancemanager == null) {

synchronized (this) {

if (_jsp_instancemanager == null) {

_jsp_instancemanager =

org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getSe

rvletConfig());

}

}

}

return _jsp_instancemanager;

}

public void _jspInit() {

}

public void _jspDestroy() {

}

public void _jspService(final javax.servlet.http.HttpServletRequest

request, final javax.servlet.http.HttpServletResponse response)

throws java.io.IOException, javax.servlet.ServletException {

final java.lang.String _jspx_method = request.getMethod();

if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) &&

!"HEAD".equals(_jspx_method) &&

!javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType()))

{

response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs

only permit GET POST or HEAD");

return;

}

final javax.servlet.jsp.PageContext pageContext;

javax.servlet.http.HttpSession session = null;

final javax.servlet.ServletContext application;

final javax.servlet.ServletConfig config;

javax.servlet.jsp.JspWriter out = null;

final java.lang.Object page = this;

javax.servlet.jsp.JspWriter _jspx_out = null;

javax.servlet.jsp.PageContext _jspx_page_context = null;

try {

response.setContentType("text/html;charset=UTF‐8");

pageContext = _jspxFactory.getPageContext(this, request, response,

null, true, 8192, true);

_jspx_page_context = pageContext;

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

_jspx_out = out;

out.write("\n");

out.write("\n");

out.write("\n");

out.write("\n");

out.write("\n");

out.write("<html>\n");

out.write(" <head>\n");

out.write(" <title>$Titlelt;/title>\n");

out.write(" </head>\n");

out.write(" <body>\n");

out.write(" ");

DateFormat dateFormat = new SimpleDateFormat("yyyy‐MM‐dd

HH:mm:ss");

String format = dateFormat.format(new Date());

out.write("\n");

out.write(" Hello , Java Server Page 。。。。\n");

out.write("\n");

out.write(" <br/>\n");

out.write("\n");

out.write(" ");

out.print( format );

out.write("\n");

out.write("\n");

out.write(" </body>\n");

out.write("</html>\n");

} catch (java.lang.Throwable t) {

if (!(t instanceof javax.servlet.jsp.SkipPageException)){

out = _jspx_out;

if (out != null && out.getBufferSize() != 0)

try {

if (response.isCommitted()) {

out.flush();

} else {

out.clearBuffer();

}

} catch (java.io.IOException e) {}

if (_jspx_page_context != null)

_jspx_page_context.handlePageException(t);

else throw new ServletException(t);

}

} finally {

_jspxFactory.releasePageContext(_jspx_page_context);

}

}

}

```

### 2、由编译后的源码解读, 可以分析出以下几点 :

1) 其类名为 index_jsp , 继承自 org.apache.jasper.runtime.HttpJspBase , 该类是HttpServlet 的子类 , 所以jsp 本质就是一个Servlet 。

2) 通过属性 _jspx_dependants 保存了当前JSP页面依赖的资源, 包含引入的外部的JSP页面、导入的标签、标签所在的jar包等,便于后续处理过程中使用(如重新编译检测,因此它以Map形式保存了每个资源的上次修改时间)。

3) 通过属性 _jspx_imports_packages 存放导入的 java 包, 默认导入 javax.servlet ,javax.servlet.http, javax.servlet.jsp 。

4) 通过属性 _jspx_imports_classes 存放导入的类, 通过import 指令导入的

DateFormat 、SimpleDateFormat 、Date 都会包含在该集合中。_jspx_imports_packages 和 _jspx_imports_classes 属性主要用于配置 EL 引擎上下文。

5) 请求处理由方法 _jspService 完成 , 而在父类 HttpJspBase 中的service 方法通过模板方法模式 , 调用了子类的 _jspService 方法。

6) _jspService 方法中定义了几个重要的局部变量 : pageContext 、Session、

application、config、out、page。由于整个页面的输出有 _jspService 方法完成,因此这些变量和参数会对整个JSP页面生效。这也是我们为什么可以在JSP页面使用这些变量的原因。

7) 指定文档类型的指令 (page) 最终转换为 response.setContentType() 方法调用。

8) 对于每一行的静态内容(HTML) , 调用 out.write 输出。

9) 对于 <% ... %> 中的java 代码 , 将直接转换为 Servlet 类中的代码。 如果在 Java代码中嵌入了静态文件, 则同样调用 out.write 输出。

### 3、编译流程--JSP 编译过程如下:

### 4、Compiler 编译工作主要包含代码生成 和 编译两部分 :

- 代码生成

1) Compiler 通过一个 PageInfo 对象保存JSP 页面编译过程中的各种配置,这些配置可能来源于 Web 应用初始化参数,也可能来源于JSP页面的指令配置(如 page ,

include)。

2) 调用 ParserController 解析指令节点,验证其是否合法,同时将配置信息保存到 PageInfo 中,用于控制代码生成。

3) 调用ParserController 解析整个页面, 由于 JSP 是逐行解析, 所以对于每一行会创建一个具体的Node 对象。如 静态文本(TemplateText)、Java代码(Scriptlet)、定制标签(CustomTag)、Include指令(IncludeDirective)。

4) 验证除指令外其他所有节点的合法性, 如 脚本、定制标签、EL表达式等。

5) 收集除指令外其他节点的页面配置信息。

6) 编译并加载当前 JSP 页面依赖的标签

7) 对于JSP页面的EL表达式,生成对应的映射函数。

8) 生成JSP页面对应的Servlet 类源代码

- 编译

代码生成完成后, Compiler 还会生成 SMAP 信息。 如果配置生成 SMAP 信息,

Compiler 则会在编译阶段将SMAP 信息写到class 文件中 。

在编译阶段, Compiler 的两个实现 AntCompiler 和 JDTCompiler 分别调用先关框架的API 进行源代码编译。

对于 AntCompiler 来说, 构造一个 Ant 的javac 的任务完成编译。

对于 JDTCompiler 来说, 调用 org.eclipse.jdt.internal.compiler.Compiler 完成编译。

**`上一节关联链接请点击`**

利刃出鞘_Tomcat 核心原理解析(三)

点击这里复制本文地址 以上内容由nimo97整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

尼墨宝库 © All Rights Reserved.  蜀ICP备2024111239号-7