1.Jsp简介

jsp(java server page),其根本是一个简化的Servlet技术,是一种动态网页技术标准。

它是在传统的网页HTML页面中插入java代码段,从而形成jsp文件,后缀为.jsp。

jsp同Servlet一样,是在服务端执行,通常返回给客户端的是一个HTML文件。

  这种动态网页技术,主要目的是将逻辑从Servlet中分离,jsp侧重于显示

2.Jsp处理方式

上文说了,Jsp本质就是Servlet,所以java处理Jsp的方式基本同Servlet一样。

    java是一门编译型语言,因为应用服务器(tomcat等)首先需要将Jsp页面转换为一个标准java类文件,然后进行编译、加载并实例化。

编译后的java类是一个Servlet实现,负责将我们在jsp页面中编写的内容输出到客户端

    1)Jsp页面采用单独的类加载器

因此重新编译不会导致整个应用重新加载,这也是我们可以在运行状态更新Jsp页面的原因

    2)提升性能方式

应用服务器会对Jsp类和实例进行缓存,并定时检测Jsp页面的更新情况,如发生变更,将会重新编译

3.Jsp编译(运行时编译)

所谓运行时编译:就是tomcat并不会再启动web应用时自动编译Jsp文件,而是在客户端第一次请求时才编译需要访问的Jsp文件

    编译过程分为:

    1)获取Jsp文件路径

默认将HttpServletRequest.getServletPath+HttpServletRequest.getPathInfo作为jsp路径

注意:还有其他两种方式,下面会通过源码来分析

    2)根据Jsp文件构造JspServletWrapper文件

JspServletWrapper为Jsp引擎的核心,它负责编译、加载Jsp文件并完成请求处理。每个Jsp页面对应一个JspServletWrapper实例。Tomcat会缓存JspServletWrapper对象以提升系统性能

    3)调用Servlet的方法完成请求处理

JspServletWrapper判断当前是否首次加载,如果是,则进行编译;如果不是,则直接调用Servlet的方法进行业务处理

    4)编译结果处理

通常默认情况下,会存放在%CATALINA_HOME%/work/Engine/Host(一般为localhost)/Context(应用名称)目录下

当然用户也可以通过配置的方式来自定义目录:

// 配置scratchdir ,该参数在默认的Server项目中web.xml中可以找到
<context-param><param-name>scratchdir</param-name><param-value>web-app/tm/jsp/</param-value>
</context-param>

4.通过源码来分析一下上述Jsp编译的过程

Jsp本质上就是Servlet

我们创建的是一个.jsp文件,但应用服务器真正使用的是一个Servlet类,是一个.java文件,那么在这个过程中究竟发生了什么呢?

首先有一个默认的知识点:tomcat在默认的web.xml中配置了一个org.apache.jasper.servlet.JspServlet,用于处理所有.jsp或者.jspx结尾的请求,该Servlet实现即为运行时编译的入口。

下面我们就来看下这个类

5.默认web.xml的观察

    1)创建SpringMVC项目

笔者创建了一个SpringMVC项目,具体过程不表

然后创建一个Controller类,请求路径为/mvc/hello,返回hello,指向一个jsp文件(hello.jsp),同时在src/main/webapp/WEB-INF/jsp/下创建hello.jsp。

在当前IDE关联tomcat,并将该web项目(命名为springweb)添加到tomcat中。

我们可以在IDE中看到一个Server项目,这个是自动创建的,如下所示

   2)观察web.xml文件

该文件是tomcat的默认web.xml,我们来看下其主要的几个项

 * DefaultServlet(默认的Servlet,当请求找不到mapping时,就会转发到这)

 <!-- The default servlet for all web applications, that serves static     --><!-- resources.  It processes all requests that are not mapped to other   --><!-- servlets with servlet mappings (defined either here or in your own   --><!-- web.xml file).  This servlet supports the following initialization   --><!-- parameters (default values are in square brackets):                  --><servlet><servlet-name>default</servlet-name><servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class><init-param><param-name>debug</param-name><param-value>0</param-value></init-param><init-param><param-name>listings</param-name><param-value>false</param-value></init-param><load-on-startup>1</load-on-startup></servlet> 

注意:读者可以仔细阅读一下相关源码,可以发现,里面基本做了所有的异常处理,403、404...

       * JspServlet(处理.jsp)

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

        * welcome-list(默认的欢迎页面)

  <!-- ==================== Default Welcome File List ===================== --><!-- When a request URI refers to a directory, the default servlet looks  --><!-- for a "welcome file" within that directory and, if present, to the   --><!-- corresponding resource URI for display.                              --><!-- If no welcome files are present, the default servlet either serves a --><!-- directory listing (see default servlet configuration on how to       --><!-- customize) or returns a 404 status, depending on the value of the    --><!-- listings setting.                                                    --><!--                                                                      --><!-- If you define welcome files in your own application's web.xml        --><!-- deployment descriptor, that list *replaces* the list configured      --><!-- here, so be sure to include any of the default values that you wish  --><!-- to use within your application.                                       --><welcome-file-list><welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file></welcome-file-list>

6.org.apache.jasper.servlet.JspServlet源码分析

    1)类结构

// The JSP engine (a.k.a Jasper)
public class JspServlet extends HttpServlet implements PeriodicEventListener {

可以看到,JspServlet本质上也是一个Servlet,也符合Servlet的一系列使用规范。

通过上面默认web.xml的分析可以看到,应用服务器启动时就会加载该类,并调用其init方法

    2)JspServlet.service()方法

主要的业务处理都在这,我们重点来看下这个方法

@Override
public void service (HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 1.jspFile可以通过配置中的init-param来构建(一般来说,我们不配置这个字段)String jspUri = jspFile;if (jspUri == null) {// 2.判断请求中的javax.servlet.include.servlet_path属性是否为空,不为空则设置为jspUri(一般来说,不配置该字段)jspUri = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);if (jspUri != null) {String pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);if (pathInfo != null) {jspUri += pathInfo;}} else {// 3.HttpServletRequest.getServletPath+HttpServletRequest.getPathInfo作为jsp路径jspUri = request.getServletPath();String pathInfo = request.getPathInfo();if (pathInfo != null) {jspUri += pathInfo;}}}// 通过上面1-3的分析,我们确认了jsp的路径...try {// 4.检查是否预编译,如果没有编译过,则在serviceJSPFile方法会先编译该Jspboolean precompile = preCompile(request);// 5.调用jsp对应的Servlet.service()方法serviceJspFile(request, response, jspUri, precompile);} catch (RuntimeException e) {throw e;} ...
}

 3)serviceJspFile(request, response, jspUri, precompile)

private void serviceJspFile(HttpServletRequest request,HttpServletResponse response, String jspUri,boolean precompile)throws ServletException, IOException {// 1.判断是否已经加载过,没有则加载// 加载的主要方式也就是包装一个JspServletWrapper,放入到rctxt中JspServletWrapper wrapper = rctxt.getWrapper(jspUri);if (wrapper == null) {synchronized(this) {wrapper = rctxt.getWrapper(jspUri);if (wrapper == null) {// Check if the requested JSP page exists, to avoid// creating unnecessary directories and files.if (null == context.getResource(jspUri)) {handleMissingResource(request, response, jspUri);return;}wrapper = new JspServletWrapper(config, options, jspUri,rctxt);rctxt.addWrapper(jspUri,wrapper);}}}try {// 2.业务处理wrapper.service(request, response, precompile);} catch (FileNotFoundException fnfe) {handleMissingResource(request, response, jspUri);}}

  总结:   

    我们将Jsp信息封装为JspServletWrapper,然后将业务处理交给JspServletWrapper处理,下面我们就来看下JspServletWrapper是如何处理的

7.org.apache.jasper.servlet.JspServletWrapper业务处理

service方法主要内容如下:

// JspServletWrapper.service(request, response, precompile)
public void service(HttpServletRequest request,HttpServletResponse response,boolean precompile)throws ServletException, IOException, FileNotFoundException {Servlet servlet;try {...// 1.如果是第一次访问service访问,则需要先编译Jsp为Servletif (options.getDevelopment() || firstTime ) {synchronized (this) {firstTime = false;ctxt.compile();}} else {if (compileException != null) {// Throw cached compilation exceptionthrow compileException;}}// 2.获取对应的Servletservlet = getServlet();} catch (ServletException ex) {...}try {// 3.对已经加载的Jsp进行处理,如果长时间不用则删除之if (unloadAllowed) {synchronized(this) {if (unloadByCount) {if (unloadHandle == null) {unloadHandle = ctxt.getRuntimeContext().push(this);} else if (lastUsageTime < ctxt.getRuntimeContext().getLastJspQueueUpdate()) {ctxt.getRuntimeContext().makeYoungest(unloadHandle);lastUsageTime = System.currentTimeMillis();}} else {if (lastUsageTime < ctxt.getRuntimeContext().getLastJspQueueUpdate()) {lastUsageTime = System.currentTimeMillis();}}}}// 4.真正的业务处理,交由具体的Servletif (servlet instanceof SingleThreadModel) {// sync on the wrapper so that the freshness// of the page is determined right before servicingsynchronized (this) {servlet.service(request, response);}} else {servlet.service(request, response);}} catch (UnavailableException ex) {...} ...
}

下面我们逐步来看下这几个方法

    1)JspCompilationContext.compile(),创建Jsp compile,主要将Jsp转换为java类,具体过程不表

public void compile() throws JasperException, FileNotFoundException {// 主要在这里,创建Compile,默认创建org.apache.jasper.compiler.JDTCompilercreateCompiler();if (jspCompiler.isOutDated()) {...}
}

    2)getServlet()获取jsp对应的Servlet

public Servlet getServlet() throws ServletException {// 已经加载过的不会再次加载,直接返回即可if (reload) {synchronized (this) {// Synchronizing on jsw enables simultaneous loading// of different pages, but not the same page.if (reload) {// This is to maintain the original protocol.destroy();final Servlet servlet;try {// 1.使用InstanceManager生成对应的Servlet类// 本例中的hello.jsp 生成 org.apache.jsp.WEB_002dINF.jsp.hello_jspInstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config);servlet = (Servlet) instanceManager.newInstance(ctxt.getFQCN(), ctxt.getJspLoader());} catch (Exception e) {Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(t);throw new JasperException(t);}// 2.调用servlet.init方法初始化servlet.init(config);if (!firstTime) {ctxt.getRuntimeContext().incrementJspReloadCount();}theServlet = servlet;reload = false;// Volatile 'reload' forces in order write of 'theServlet' and new servlet object}}}return theServlet;
}

3)servlet.service(request, response)到这里就将请求转发给特定的Servlet去处理了

总结:

    最终tomcat编译器将hello.jsp编译成了hello_jsp.java,该类继承了HttpServlet。

    所以,正验证了开头我们说的:Jsp本质上就是Servlet

8.hello_jsp.java展示

最后我们来展示一下hello.jsp以及生成后的hello_jsp.java类

1)hello.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD//XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head><meta http-equiv="content-type" content="text/html;charset=utf-8"/><title>九九乘法表</title>
</head>
<body>
<br/>
<form id="form1" name="form1" method="post" action="result.jsp"><p align="center">请输入两个自然数给您打印乘法表</p><p align="center">要求:startNumber &lt;endNumber <br/></p><table width="350" border="1" align="center" cellpadding="0"cellspacing="0" bgcolor="#aaccdd" bordercolor="#cccccc"><tr><td width="101">startNumber:</td><td width="113"><label><input name="s" type="text" id="textfield" size="15" maxlength="8" height="20"/></label></td><td width="68">&nbsp;<br/></td></tr><tr><td>endNumber</td><td><label><input name="e" type="text" id="textfield2" size="15" maxlength="8" height="20"/></label></td><td>&nbsp;<br/></td></tr><tr><td>&nbsp;</td><td><label><input type="submit" name="button" id="button" value="submit"/><input name="button2" type="reset" id="button2" value="reset"/></label></td><td>&nbsp;</td></tr></table>
</form>
</body>
</html>

 2)hello_jsp.java(目录为%CATALINA_HOME%\work\Catalina\localhost\springweb\org\apache\jsp\WEB_002dINF\jsp)

/** Generated by the Jasper component of Apache Tomcat* Version: Apache Tomcat/8.5.31* Generated at: 2018-11-28 01:27:32 UTC* Note: The last modified time of this file was set to*       the last modified time of the source file after*       generation to assist with modification tracking.*/
package org.apache.jsp.WEB_002dINF.jsp;import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBaseimplements org.apache.jasper.runtime.JspSourceDependent,org.apache.jasper.runtime.JspSourceImports {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;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 = null;}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().getServletContext()).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(getServletConfig());}}}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("\r\n");out.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD//XHTML 1.0 Transitional//EN\"\r\n");out.write("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n");out.write("<html>\r\n");out.write("<head>\r\n");out.write("    <meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"/>\r\n");out.write("    <title>九九乘法表</title>\r\n");out.write("</head>\r\n");out.write("<body>\r\n");out.write("<br/>\r\n");out.write("<form id=\"form1\" name=\"form1\" method=\"post\" action=\"result.jsp\">\r\n");out.write("    <p align=\"center\">请输入两个自然数给您打印乘法表</p>\r\n");out.write("    <p align=\"center\">要求:startNumber &lt;endNumber <br/></p>\r\n");out.write("    <table width=\"350\" border=\"1\" align=\"center\" cellpadding=\"0\"\r\n");out.write("           cellspacing=\"0\" bgcolor=\"#aaccdd\" bordercolor=\"#cccccc\">\r\n");out.write("        <tr>\r\n");out.write("            <td width=\"101\">startNumber:</td>\r\n");out.write("            <td width=\"113\">\r\n");out.write("                <label>\r\n");out.write("                    <input name=\"s\" type=\"text\" id=\"textfield\" size=\"15\" maxlength=\"8\" height=\"20\"/>\r\n");out.write("                </label>\r\n");out.write("            </td>\r\n");out.write("            <td width=\"68\">&nbsp;<br/></td>\r\n");out.write("        </tr>\r\n");out.write("        <tr>\r\n");out.write("            <td>endNumber</td>\r\n");out.write("            <td>\r\n");out.write("                <label>\r\n");out.write("                    <input name=\"e\" type=\"text\" id=\"textfield2\" size=\"15\" maxlength=\"8\" height=\"20\"/>\r\n");out.write("                </label>\r\n");out.write("            </td>\r\n");out.write("            <td>&nbsp;<br/></td>\r\n");out.write("        </tr>\r\n");out.write("        <tr>\r\n");out.write("            <td>&nbsp;</td>\r\n");out.write("            <td>\r\n");out.write("                <label>\r\n");out.write("                    <input type=\"submit\" name=\"button\" id=\"button\" value=\"submit\"/>\r\n");out.write("                    <input name=\"button2\" type=\"reset\" id=\"button2\" value=\"reset\"/>\r\n");out.write("                </label>\r\n");out.write("            </td>\r\n");out.write("            <td>&nbsp;</td>\r\n");out.write("        </tr>\r\n");out.write("    </table>\r\n");out.write("</form>\r\n");out.write("</body>\r\n");out.write("</html>");} 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);}}
}

参考:Tomcat架构解析(刘光瑞)

Tomcat源码解析:Jsp文件的编译、实现相关推荐

  1. Tomcat源码解析系列二:Tomcat总体架构

    Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...

  2. Tomcat源码解析五:Tomcat请求处理过程

    前面已经分析完了Tomcat的启动和关闭过程,本篇就来接着分析一下Tomcat中请求的处理过程. 在开始本文之前,咋们首先来看看一个Http请求处理的过程,一般情况下是浏览器发送http请求-> ...

  3. Tomcat 源码解析一初识

      为什么我想研究Tomcat源码,我们现在都用的是SpringBoot开发项目,而SpringBoot对错Tomcat集成,导致现在基本上看不到Tomcat的身影了,但是Tomcat不存在吗?只要我 ...

  4. Tomcat源码解析一:下载源码与导入eclipse

    自从写web程序以来,web程序是如何在Tomcat中运行的一直困惑着我,不知道底层的运行机制是无法真正理解web的,所以就开始研究Tomcat源码,Tomcat是一个轻量级的java服务器,再结合& ...

  5. Tomcat源码解析:环境搭建

    下载源码 从github下载tomcat源码 git clone https://github.com/apache/tomcat.git 或者直接fork一份到自己仓库,以便后续添加注释,附上笔者自 ...

  6. Tomcat源码解析三:tomcat的启动过程

    Tomcat组件生命周期管理 在Tomcat总体结构 (Tomcat源代码解析之二)中,我们列出了Tomcat中Server,Service,Connector,Engine,Host,Context ...

  7. 【Vue.js源码解析 三】-- 模板编译和组件化

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 模板编译 模板编译的主要目的是将模板 (template) 转换为渲染函数 (render) <div> ...

  8. Tomcat源码解析:2、Tomcat结构组成

    在看源码之前,我们首先得了解Tomcat的结构组成,并且了解它每个组件的作用,这样我们看才不会一脸懵逼. 总体结构 tomcat的总体架构如下图所示(摘自https://blog.csdn.net/D ...

  9. Python文件用pyinstaller打包成.exe文件后如何获取Python源码(Python文件反编译)

    此文章自己做个学习记录,也希望对跟我有一样困扰的同学一些帮助! 使用到的工具下载链接我都已经附上,点击下面蓝色字体可直接下载: 1.pyinstxtractor.py 2.wxMEDIT 3.在线编译 ...

最新文章

  1. R语言ggplot2可视化绘制二维的密度图:在R中建立二维散点数据的连续密度热图、2D密度估计、MASS包中的kde2d函数实现2D密度估计、geom_density2d函数可视化二维密度图
  2. 以Java的视角来聊聊BIO、NIO与AIO的区别
  3. 离散数学实验题目-图
  4. CSS三个非常重要的特性分享!
  5. linux中下载文件的命令
  6. JS_17 ES5,ES6
  7. 8.8 Sencha Studio 监视工具( Inspect Tool)
  8. 贝叶斯分类器基本理论
  9. 【vtk实例】平面切割
  10. 线性回归的 拟合优度R^2 与 相关系数
  11. 提示fxp不是一个目标文件
  12. 京东淘宝,拼多多三大电商平台竞品分析
  13. Word 2003 长篇文档排版技巧(二)
  14. php程序打包exe文件下载,手把手教你把前端代码打包成msi和exe文件
  15. 遇文件尾测试结束c语言,Fortran和C语言测试文件尾的方法
  16. 在RT-Thread STM32F407平台下配置SPI flash为U盘
  17. svg嵌套svg_使用SVG掩盖效果
  18. Intellij IDEA Messages报Information:java: javacTask: 源发行版 8 需要目标发行版 1.8 Information:java: Errors occu
  19. C语言面试考点之一(常见关键字)
  20. 卡方分布的概率密度公式推导

热门文章

  1. Flink应用——公交疫情实时流监控
  2. “沉迷”学习,神奇的“峰终定律”
  3. what is long tail effect
  4. 微信小程序-转盘抽奖
  5. 赛灵思宣布收购深鉴科技
  6. 发明专利申请流程以及时间
  7. 高通平台 android 12 定时开关机
  8. Error updating changes: Out of memory, malloc failed (tried to allocate 1048576 bytes)
  9. 靠着这份900多页的PDF面试整理,知乎上转疯了!
  10. 网络状态诊断工具——netstat命令