Jasper

mac2024-05-10  10

3.1 Jasper简介

对于基于JSP的web应用来说,我们可以直接在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文件。

3.2 JSP编译方式

3.2.1 运行时编译

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

创建一个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>

3.2.1.1编译过程

Tomcat在默认的web.xml中配置了一个org.apache.jasper.servlet.JspServlet,用于处理所有的.jsp或.jspx结尾的请求,该Servlet实现即时运行时编译的入口。 JspServlet处理流程图:

3.2.1.2 编译结果

1)如果在tomcat/conf/web.xml中配置了参数scratchdir,则jsp编译后的结果,就会存储在该目录下。 2)如果没有配置该选项,则会将编译后的结果,存储在Tomcat安装目录下的work/Catalina(Engine名称)/localhost(Host名称)/Context名称。

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

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

3.2.2 预编译

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

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

3.3JSP编译原理

3.3.1代码分析

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

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase implements 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; 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().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, "JSPsonly 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>$Title$</title>\n"); out.write(" </head>\n"); out.write(" <body>\n"); out.write(" "); DateFormat dateFormat = new SimpleDateFormat("yyyy‐MM‐ddHH: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); } } }

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

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

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

3)通过属性_jsp_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.3.2 编译流程

JSP编译过程如下: Compiler编译工作主要包含代码生成和编译两部分:

代码生成

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

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

3)调用ParseController解析整个页面,由于JSP是逐行解析,所以对于每一行会创建一个具体的Node对象。如静态文本(TemplateText)、Java代码(Script)、定制标签(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完成编译。

最新回复(0)