1. 简介
对于基于JSP的web应用来说,我们可以直接在JSP页面中编写java代码,添加第三方的标签库,以及使用EL表达式,但是无论经过何种形式的处理,最终输出到客户端的都是标准的HTML页面(包含JS,CSS。。。),并不包含任何java相关的语法,也就是说,我们可以把jsp看做是一种运行在服务器端的脚本,那么服务器如何将JSP转换成HTML页面了?
Jasper模块是tomcat的JSP核心引擎,我们知道JSP的本质是一个Servlet,tomcat使用Jasper对JSP语法进行解析,生成Service并生成Class字节码,用户在访问jsp的时候,会访问Servlet,最终将访问的结果直接响应在浏览器。另外,在运行的时候,Jasper还会检测JSP文件是否被修改,如果被修改,则会重新编译JSP文件。
2. JSP编译方式
2.1 运行时编译
tomcat并不会在启动web应用的时候自动编译JSP文件,而是在客户端第一次请求的时候,才编译需要访问的JSP文件。
2.1.1 编译过程
tomcat在默认的web.xml中配置了一个org.apache.jasper.servlet.JspServlet,用于处理所以的.jsp或者.jspx结尾的请求
<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>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- The mappings for the JSP servlet -->
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>
2.1.2 案例演示
创建一个web项目,并编写JSP代码
1. 创建一个4.0的servlet的web工程,如图:
注意:该工程是一个4.0的servlet的web工程,可以使用注解
2. 在index.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>hello</title>
</head>
<body>
<%
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = format.format(new Date());
%>
hello ,Java server page...
<br/>
<%= dateStr %>
</body>
</html>
注意:暂时不需要写servlet,先运行看看效果,如图:
我们发现,浏览器返回了我们脚本程序中定义的时间字符串。
2.1.3 案例分析
我们发现,我们在地址栏中并没有输入index.jsp,但是浏览器还是可以返回该页面的信息,这是因为,没有指定访问页面的时候,默认访问的就是当前工程下的index.jsp,当访问的是index.jsp的时候,这个时候,请求会被tomcat的JspServlet所拦截,然后JspServlet开始处理该请求。
JspServlet的处理流程图:
1. 首先用户在访问的时候,访问的是我们的JSP,访问这个请求到达服务端之后,服务端会去调用JSPServlet的service()来进行处理。
2. 在JSPServlet中调用service方法:
2.1 service里面的逻辑首先获取jsp的文件路径
2.2 判断当前是否为预编译请求
2.3 执行serviceJspFile()方法
2.4 获取JspServletWrapper对象
3. 获取到JspServletWrapper对象,去调用其service方法,对jsp文件进行源码的生成然后在进行编译,最终调用这个JSP生成的servlet来执行对应的请求。
2.1.4 源码跟踪分析
我们将我们的项目编译之后的目录复制到tomcat的工程的home/weapps下,如图:
启动源码程序,在浏览器输入网址http://localhost:8080/webTest/,浏览器正常返回,如图:
然后我们重新以debug模式开启tomcat源码项目,在JspServlet.java的service方法处打一个断点,如图:
我们在浏览器回车,程序走到断点处,jspUri=null,程序走到
jspUri = request.getServletPath();
这时候,jspUri=“/index.jsp”,这是因为,我们虽然没有指定index.jsp,但是在tomcat的web.xml的配置文件中有如下配置
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
程序会依次找index.html、index.htm、index.jsp这三个文件,如果找到了index.html,那么返回index.html,找到了index.jsp就返回index.jsp。
接着断点往下走,走到
boolean precompile = preCompile(request);
判断是否为预编译请求,然后调用serviceJSPService方法,将是否预编译返回值传过去,如图:
进入serviceJSPService,首先就是获取JspServletWrapper对象
JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
拿到wrapper对象之后,再去调用其service方法
wrapper.service(request, response, precompile);
进入wrapper的service方法
if (options.getDevelopment() || mustCompile) {
// The following sets reload to true, if necessary
ctxt.compile();
mustCompile = false;
}
在进入compile方法,执行JSPCompile.compile方法,如图:
在进入compile,
开始编译jsp
注意:generateJava生成java文件,generateClass生成class文件
编译结果存放在哪里了?
1)如果在tomcat/conf/web.xml中配置了参数scratchdir,则JSP编译后的结果,就会存储在该目录下。
<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,那么,默认目录为:work/Catalina/localhost/jsp_demo_01
3) 如果使用的是idea开发根据集成tomcat访问web工程中的jsp,编译后的结果存放在idea的tomcat的work/Catalina(Engine名称)/localhost(Host名称)/Context名称中。
生成了java文件,生成了class字节码文件,然后回到JspServletWraper,如图:
接着运行,获取生成是字节码对象
接着执行index_jsp的service方法了
调用生成的servlet,将需要的信息通过out输出到浏览器,如图:
2.2 预编译
预编译就是在web应用启动的时候,一次性的将web应用中的所有的JSP页面一次性的编译完成,在这种情况下,web应用运行过程中,便可以不必再进行实时编译,而是直接调用JSP页面对应的Servlet完成请求处理,从而提升系统性能。
Tomcat提供了一个shell程序JspC,用于支持JSP预编译,而且在tomcat的安装目录下提供了一个Catalina-task.xml文件,声明了Tomcat支持的Ant任务,因此,我们很容易使用Ant来执行JSP预编译。(要使用这种方式,需要确保在此之前已经下载并安装了Apache Ant)。