09 Tomcat Jasper(JSP引擎)详解

在这里插入图片描述

Tomcat模块

Jasper (JSP引擎)简介

JSP页面中编写 Java代码,添加第三方的标签库,以及使用EL表达式。但是无论经过何种形式的处理,最终输出到客户端的都是标准的HTML页面(包含js ,css…),并不包含任何的java相关的语法。 故jsp看做是一种运行在服务端的脚本。

服务器如何将 JSP页面转换为HTML页面?

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


JSP 编译方式

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

使用标签库 <%@ taglib prefix=“c” uri=“http://java.sun.com/jsp/jstl/core” %> 需要加载标签库jar

在这里插入图片描述

创建一个web项目, 创建一个jsp页面如下 :

<%@ 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>$Title$</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>

启动项目 访问web页面
在这里插入图片描述
在这里插入图片描述


运行时 编译过程

Tomcat 在默认的web.xmlorg.apache.jasper.servlet.JspServlet,用于处理所有的.jsp 或 .jspx 结尾的请求,该Servlet 实现即是运行时编译的入口。

    <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</url-pattern>
        <url-pattern>*.jspx</url-pattern>
    </servlet-mapping>

JspServlet 处理流程图
在这里插入图片描述


编译结果 - 存放位置

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

<init-param>
     <param-name>scratchdir</param-name>
     <param-value>D:/tomcat-temp</param-value>
 </init-param>

如果没有配置该选项, 则会将编译后的结果,存储在Tomcat安装目录下的work/Catalina(Engine名称)/localhost(Host名称)/Context名称
在这里插入图片描述
如果使用的是 IDEA 开发工具集成Tomcat 访问web工程中的jsp , 编译后的结果,存放在
在这里插入图片描述


Tomcat Jsp预编译

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

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


JSP编译原理

在这里插入图片描述

index_jsp.class 文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.jsp;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.el.ExpressionFactory;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspFactory;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.SkipPageException;
import org.apache.jasper.runtime.HttpJspBase;
import org.apache.jasper.runtime.InstanceManagerFactory;
import org.apache.jasper.runtime.JspSourceDependent;
import org.apache.jasper.runtime.JspSourceImports;
import org.apache.tomcat.InstanceManager;

public final class index_jsp extends HttpJspBase implements JspSourceDependent, JspSourceImports {
    private static final JspFactory _jspxFactory = JspFactory.getDefaultFactory();
    private static Map<String, Long> _jspx_dependants = new HashMap(2);
    private static final Set<String> _jspx_imports_packages;
    private static final Set<String> _jspx_imports_classes;
    private volatile ExpressionFactory _el_expressionfactory;
    private volatile InstanceManager _jsp_instancemanager;

    static {
        _jspx_dependants.put("/WEB-INF/lib/standard.jar", 1656837029008L);
        _jspx_dependants.put("jar:file:/D:/Program%20IDE/Tomcat/apache-tomcat-8.5.42/webapps/web-tomcat/WEB-INF/lib/standard.jar!/META-INF/c.tld", 1098682290000L);
        _jspx_imports_packages = new 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 HashSet();
        _jspx_imports_classes.add("java.util.Date");
        _jspx_imports_classes.add("java.text.SimpleDateFormat");
        _jspx_imports_classes.add("java.text.DateFormat");
    }

    public index_jsp() {
    }

    public Map<String, Long> getDependants() {
        return _jspx_dependants;
    }

    public Set<String> getPackageImports() {
        return _jspx_imports_packages;
    }

    public Set<String> getClassImports() {
        return _jspx_imports_classes;
    }

    public ExpressionFactory _jsp_getExpressionFactory() {
        if (this._el_expressionfactory == null) {
            synchronized(this) {
                if (this._el_expressionfactory == null) {
                    this._el_expressionfactory = _jspxFactory.getJspApplicationContext(this.getServletConfig().getServletContext()).getExpressionFactory();
                }
            }
        }

        return this._el_expressionfactory;
    }

    public InstanceManager _jsp_getInstanceManager() {
        if (this._jsp_instancemanager == null) {
            synchronized(this) {
                if (this._jsp_instancemanager == null) {
                    this._jsp_instancemanager = InstanceManagerFactory.getInstanceManager(this.getServletConfig());
                }
            }
        }

        return this._jsp_instancemanager;
    }

    public void _jspInit() {
    }

    public void _jspDestroy() {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String _jspx_method = request.getMethod();
        if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !DispatcherType.ERROR.equals(request.getDispatcherType())) {
            response.sendError(405, "JSPs only permit GET POST or HEAD");
        } else {
            JspWriter out = null;
            JspWriter _jspx_out = null;
            PageContext _jspx_page_context = null;

            try {
                response.setContentType("text/html; charset=UTF-8");
                PageContext pageContext = _jspxFactory.getPageContext(this, request, response, (String)null, true, 8192, true);
                _jspx_page_context = pageContext;
                pageContext.getServletContext();
                pageContext.getServletConfig();
                pageContext.getSession();
                out = pageContext.getOut();
                out.write("\r\n");
                out.write("\r\n");
                out.write("\r\n");
                out.write("\r\n");
                out.write("\r\n");
                out.write("<html>\r\n");
                out.write("<head>\r\n");
                out.write("<title>$Title$</title>\r\n");
                out.write("</head>\r\n");
                out.write("<body>\r\n");
                DateFormat dateFormat = new SimpleDateFormat("yyyy‐MM‐dd HH:mm:ss");
                String format = dateFormat.format(new Date());
                out.write("\r\n");
                out.write("Hello , Java Server Page 。。。。\r\n");
                out.write("<br/>\r\n");
                out.print(format);
                out.write("\r\n");
                out.write("</body>\r\n");
                out.write("</html>");
            } catch (Throwable var15) {
                if (!(var15 instanceof SkipPageException)) {
                    out = (JspWriter)_jspx_out;
                    if (_jspx_out != null && ((JspWriter)_jspx_out).getBufferSize() != 0) {
                        try {
                            if (response.isCommitted()) {
                                out.flush();
                            } else {
                                out.clearBuffer();
                            }
                        } catch (IOException var14) {
                        }
                    }

                    if (_jspx_page_context == null) {
                        throw new ServletException(var15);
                    }

                    _jspx_page_context.handlePageException(var15);
                }
            } finally {
                _jspxFactory.releasePageContext(_jspx_page_context);
            }

        }
    }
}

由编译后的源码解读, 分析如下 :

  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 输出。

JSP 编译过程

在这里插入图片描述
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 完成编译。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EngineerForSoul

你的鼓励是我孜孜不倦的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值