在Java Web的开发过程中,可能有过这样的疑问,Tomcat是一个Servlet运行环境(容器),所有经过Tomcat的请求都是由一个Servlet来处理的。Servlet是一个Java类,可是JSP不是,那JSP又是怎么在Tomcat里面运行的呢?
事实上,JSP是Servlet的一种特殊形式,每个JSP页面就是一个Servlet实例——JSP页面由系统编译成Servlet,Servlet再负责响应用户请求。JSP其实也是Servlet的一种简化,使用JSP时,其实还是使用Servlet,因为Web应用中的每个JSP页面都会由Servlet容器生成对应的Servlet。
根据上面的分析,我们又有一个疑问:为什么说JSP就是一个Servlet,是根据什么来判定的呢?
1 为什么说JSP是Servlet
首先在IntelljIdea里面编写一个简单的test.jsp页面
当启动Tomcat后,可以在Tomcat的\work\Catalina\localhost\ROOT\org\apache\jsp目录下找到如下文件:
这两个文件便是test.jsp经过系统编译生成的对应的 .java文件和 .class文件,打开test_jsp.java文件
可以看到,这个test_jsp类继承了HttpJspBase,并且实现了org.apache.jasper.runtime.JspSourceDependent与org.apache.jasper.runtime.JspSourceImports这两个接口,我们知道判定一个类是Servlet的方法是看这个类是否间接或直接实现了Servlet接口,所以我们要看test_jsp这个类是否间接或直接实现了该接口,这里它并没有直接实现Servlet接口,所以现在我们还不能从这里看出test_jsp这个类就是一个Servlet,我们便可以猜想,会不会是HttpJspBase这个类实现了此接口呢。
于是我们进一步分析HttpJspBase这个类的继承结构。
我们去查看HttpJspBase类的源码
从上面就可以看出,该类继承了HttpServlet,我们之前便知道HttpServlet间接实现了Servlet接口。如果不知道的我们可以在IntelljIdea里面将鼠标定位在HttpJspBase上面按住ctrl+alt+u查看他的继承关系图:
图 1-1 HttpJspBase继承关系
到这里我们便能清晰的知道为什么说JSP是一个Servlet了。
2 Tomcat处理JSP文件流程分析
经过上面的分析,我们已经知道了JSP就是一个Servlet。那JSP又是如何转换成Servlet的呢?每一个Servlet都需要在web.xml 文件进行配置,这样浏览器才能访问得到这个Servlet。可是现在这个test.jsp并没有在我们项目的web.xml里面做任何配置,那浏览器是怎么访问到jsp页面的呢?回答这个问题之前我们先来了解一下Tomcat如何响应静态资源。
DefaultServlet介绍
本质上讲,Tomcat对于所有的静态资源会做统一处理,也就是在所有你没有配置URL匹配的地方,Tomcat这个全局统一处理的配置就开始接管工作了。在Tomcat的conf目录下面可以看到有一个web.xml文件,打开后你会发现这样的说明:
在向下你会看到关于这个全局处理的Servlet声明,如下图
这个DefaultServlet的servlet-mapping是这样配置的:
到这里不禁有人会问,既然url-pattern 配置的是 / ,那不就应该可以响应所有的请求了吗?其实上面的图中已经给出了解释,事实上这是匹配所有你没有定义的Servlet-mapping的请求。之所以自己定义的Servlet可以优先生效,则是因为Tomcat内的Servlet的mapping配置是严格按照声明顺序初始化,并按此顺序响应请求,一层层按此比对,有一个可以响应请求,就用其处理。有相关疑问可以参考一下博文:
http://k1121.iteye.com/blog/1564241
综上所述,所有经过Tomcat的请求都是由一个servlet来处理的,如果一个请求没有匹配到任何应用指定的Servlet,那么就会流到Tomcat的默认的servlet来,这个Servlet名字叫做DefaultServlet,DefaultServlet是配置在/conf/web.xml里面。
JspServlet介绍
上一节我们了解到Tomcat使用DefaultServlet处理所有的静态资源。这一节我们来看一个jsp请求又是怎么被响应的。同DefaultServlet类似,jsp的处理也不需要我们单独配置,而是在/conf/web.xml中做为全局配置存在。其对应的处理类为JspServlet,用于处理所有的jsp请求。同样我们打开/conf/web.xml,可以看到以下代码与注释:
通过看注释我们便对该配置的作用一目了然,往下看我们会看到JspServlet的mapping配置,其url-pattern为*.jsp和*.jspx。所以它可以拦截所有的jsp请求并作出相应的反应。
到这里我们便知道了为什么浏览器在我们自己没对jsp文件做任何配置的情况下依旧能访问的原因。
JSP转化为Servlet
在文章开头我们知道当Tomcat启动过后,一个xxx.jsp文件会在\org\apache\jsp目录下生成相应的xxx_jsp.java文件与xxx_jsp.class文件,打开我们之前已经生成的test_jsp.java 文件,这个文件结构如下图所示:
主要的转换动作是在方法_japService()中实现的,如下的Servlet类的代码截图可以看出,其中插入了session、application等对象的初始化,这几个对象都是通过页面级别的对象pageContext获取到的。
页面中的java代码去哪儿了呢,转换过程中,HTML页面元素内容可以理解为通过out.write()直接输出给前端页面,java代码(<%%>包含的内容)直接去掉<%%>写到类中执行。部分代码截图如下:
图中红色方框内的内容就是我们在jsp页面中<% %>中的Java代码。在转化中直接去除<% %>后放到类代码中,而其余的可以理解为直接out.write()输出给前端页面。至此我们便解释了Tomcat如何处理jsp文件的问题。
3 总结
本文对JSP的运行原理进行了详细的分析,可以得出下面的流程图:
图3-1 jsp页面工作原理图
根据上面的JSP页面工作原理图,可以得到如下结论:
— JSP文件必须在JSP服务器内运行。
— JSP文件必须生成Servlet才能执行。
JSP和Servlet会有如下转换:
- JSP页面的静态内容、JSP脚本都会转换成Servlet的xxxService()方法,类似于自行创建Servlet时service()方法。
- JSP声明部分,转换成Servlet的成员部分。所有JSP声明部分可以使用private,protected,public,static等修饰符,其他地方则不行。
- JSP的输出表达式(<%= ..%>部分),输出表达式会转换成Servlet的xxxService()方法里的输出语句。
- 九个内置对象要么是xxxService()方法的形参,要么是该方法的局部变量,所以九个内置对象只能在JSP脚本和输出表达式中使用。