Tomcat源码解析:Jsp文件的编译、实现
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 <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"> <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> <br/></td></tr><tr><td> </td><td><label><input type="submit" name="button" id="button" value="submit"/><input name="button2" type="reset" id="button2" value="reset"/></label></td><td> </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 <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\"> <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> <br/></td>\r\n");out.write(" </tr>\r\n");out.write(" <tr>\r\n");out.write(" <td> </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> </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文件的编译、实现相关推荐
- Tomcat源码解析系列二:Tomcat总体架构
Tomcat即是一个HTTP服务器,也是一个servlet容器,主要目的就是包装servlet,并对请求响应相应的servlet,纯servlet的web应用似乎很好理解Tomcat是如何装载serv ...
- Tomcat源码解析五:Tomcat请求处理过程
前面已经分析完了Tomcat的启动和关闭过程,本篇就来接着分析一下Tomcat中请求的处理过程. 在开始本文之前,咋们首先来看看一个Http请求处理的过程,一般情况下是浏览器发送http请求-> ...
- Tomcat 源码解析一初识
为什么我想研究Tomcat源码,我们现在都用的是SpringBoot开发项目,而SpringBoot对错Tomcat集成,导致现在基本上看不到Tomcat的身影了,但是Tomcat不存在吗?只要我 ...
- Tomcat源码解析一:下载源码与导入eclipse
自从写web程序以来,web程序是如何在Tomcat中运行的一直困惑着我,不知道底层的运行机制是无法真正理解web的,所以就开始研究Tomcat源码,Tomcat是一个轻量级的java服务器,再结合& ...
- Tomcat源码解析:环境搭建
下载源码 从github下载tomcat源码 git clone https://github.com/apache/tomcat.git 或者直接fork一份到自己仓库,以便后续添加注释,附上笔者自 ...
- Tomcat源码解析三:tomcat的启动过程
Tomcat组件生命周期管理 在Tomcat总体结构 (Tomcat源代码解析之二)中,我们列出了Tomcat中Server,Service,Connector,Engine,Host,Context ...
- 【Vue.js源码解析 三】-- 模板编译和组件化
前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 模板编译 模板编译的主要目的是将模板 (template) 转换为渲染函数 (render) <div> ...
- Tomcat源码解析:2、Tomcat结构组成
在看源码之前,我们首先得了解Tomcat的结构组成,并且了解它每个组件的作用,这样我们看才不会一脸懵逼. 总体结构 tomcat的总体架构如下图所示(摘自https://blog.csdn.net/D ...
- Python文件用pyinstaller打包成.exe文件后如何获取Python源码(Python文件反编译)
此文章自己做个学习记录,也希望对跟我有一样困扰的同学一些帮助! 使用到的工具下载链接我都已经附上,点击下面蓝色字体可直接下载: 1.pyinstxtractor.py 2.wxMEDIT 3.在线编译 ...
最新文章
- R语言ggplot2可视化绘制二维的密度图:在R中建立二维散点数据的连续密度热图、2D密度估计、MASS包中的kde2d函数实现2D密度估计、geom_density2d函数可视化二维密度图
- 以Java的视角来聊聊BIO、NIO与AIO的区别
- 离散数学实验题目-图
- CSS三个非常重要的特性分享!
- linux中下载文件的命令
- JS_17 ES5,ES6
- 8.8 Sencha Studio 监视工具( Inspect Tool)
- 贝叶斯分类器基本理论
- 【vtk实例】平面切割
- 线性回归的 拟合优度R^2 与 相关系数
- 提示fxp不是一个目标文件
- 京东淘宝,拼多多三大电商平台竞品分析
- Word 2003 长篇文档排版技巧(二)
- php程序打包exe文件下载,手把手教你把前端代码打包成msi和exe文件
- 遇文件尾测试结束c语言,Fortran和C语言测试文件尾的方法
- 在RT-Thread STM32F407平台下配置SPI flash为U盘
- svg嵌套svg_使用SVG掩盖效果
- Intellij IDEA Messages报Information:java: javacTask: 源发行版 8 需要目标发行版 1.8 Information:java: Errors occu
- C语言面试考点之一(常见关键字)
- 卡方分布的概率密度公式推导
热门文章
- Flink应用——公交疫情实时流监控
- “沉迷”学习,神奇的“峰终定律”
- what is long tail effect
- 微信小程序-转盘抽奖
- 赛灵思宣布收购深鉴科技
- 发明专利申请流程以及时间
- 高通平台 android 12 定时开关机
- Error updating changes: Out of memory, malloc failed (tried to allocate 1048576 bytes)
- 靠着这份900多页的PDF面试整理,知乎上转疯了!
- 网络状态诊断工具——netstat命令