zhangxiaojun

Servlet和JSP的工作原理

Servlet是Java Web的基础,JSP是做Java Web开发时,每个开发人员都会用到的东东,但是对于这两者的工作原理以及他们之间的联系,我却一直云里雾里。

前两天项目中遇到了一个蹊跷的问题:我们的项目是以war包部署在Jetty中的,刚部署时运行良好,过了一段时间,请求war包中的有些页面时(如index.jsp),Jetty无法找到这些JSP页面了,于是直接返回404错误。当时没找出问题的原因,于是直接重启Jetty作为权宜之际。为了找出问题的根源,感觉很有必要弄清楚Jetty是如何处理Servlet、JSP、war包的。

Servlet和JSP的用法示例

先来看一下示例程序的文件结构:

 

运行src中的ServletServer.java会启动嵌入式的Jetty,然后接收Servlet请求;运行JspServer.java也会启动嵌入式的Jetty,然后接收JSP请求。要运行servlet,只需要引入lib/jetty目录下的jar包就可以了,要运行JSP,还需要引入lib/jsp目录下的jar包。

ServletServer.java代码如下:

 1 public class ServletServer {
 2     public static void main(String[] args) throws Exception {
 3         Server server = new Server(8080);
 4         ServletHandler handler = new ServletHandler();
 5         server.addHandler(handler);
 6         ServletHolder holder = new ServletHolder(new FirstServlet());
 7         handler.addServletWithMapping(holder, "/first");
 8         server.start();
 9         server.join();
10     }
11 }

 

 

它指定在8080端口启动Jetty,然后向Jetty中添加一个Servlet,该Servlet为FirstServlet.java,并且配置访问该Servlet的路径为:http://localhost:8080/first。

FirstServlet.java代码如下:

1 public class FirstServlet extends HttpServlet{
2     
3     public void doGet(HttpServletRequest request, HttpServletResponse response)
4             throws ServletException, IOException {
5         response.setContentType("text/html");
6         response.getWriter().println("<h1>hello</h1>");
7     }
8 
9 }

 

 

这个Servlet很简单,就是向浏览器返回一串HTML代码,于是浏览器就能看到结果了,就是显示一个字符串hello。理论上说,有了Servlet我们就能进行Java Web编程了,但是直接使用Servlet实在很不方便,因为Servlet是在java代码中嵌入HTML代码,于是就有了JSP。

         接下来看看JspServer.java的代码:

 1 public class JspServer {
 2     public static void main(String[] args) {
 3         Server serverHttp = new Server(8080);
 4 
 5         serverHttp.setSendServerVersion(false);
 6         serverHttp.setSendDateHeader(false);
 7         serverHttp.setStopAtShutdown(true);
 8         
 9         WebAppContext wac = new WebAppContext();
10         wac.setWar("test.war");
11         
12         serverHttp.setHandler(wac);
13         try {
14             serverHttp.start();
15         } catch (Exception e) {
16             e.printStackTrace();
17         }
18     }
19 }

 

 

         它也指定在8080端口启动jetty,然后指定jetty可以处理webapps目录下的JSP文件。目前webapps目录下只有一个JSP文件,hello.jsp:

1 <h1><%="hello"%></h1>

 

 

浏览器请求http://localhost:8080/hello.jsp时得到的返回也是一个字符串hello。

         比较FirstServlet.java和hello.jsp可以发现,Servlet是在java代码中嵌入HTML,而JSP是在HTML中嵌入java。为了实现java和HTML分离,还可以引入JSTL和Spring MVC等,这是后话。

Servlet的工作原理(以Jetty为例)

当我们在浏览器中请求一个Servlet时,会发生什么呢?回头看看前面ServletServer.java的代码,其中有这样一行:ServletHandler handler = new ServletHandler()。ServletHander就是用来处理Servlet请求的,它管理着Servlet的整个生命周期。Servlet的生命周期包括:初始化(init)、运行(service)和销毁(destroy),这个从javax.servlet.Servlet的接口就可以清楚的看出来。Servlet对HTTP请求的响应,是在service()方法中完成的。

示例中的FirstServlet继承自javax.servlet.http.HttpServlet,并覆盖了doGet()方法。HttpServlet的service()方法会调用doGet()方法。

下面分别来看看Servlet初始化和运行的流程。Servlet的初始化是在Jetty启动时进行的,时序图为:

 

         Jetty启动后就可以接收并处理Servlet请求,Servlet运行的时序图如下所示:

 

JSP到Servlet的转换(以Jetty为例)

JSP改变了Servlet在Java代码中写HTML代码的方式,极大的方便了开发人员,但是Jetty是只能处理Servlet的,所以必然会先把JSP转变成Servlet。示例中的hello.jsp转变成Servlet后的文件名为:hello_jsp.java,大致代码如下:

 1 public final class hello_jsp extends org.apache.jasper.runtime.HttpJspBase
 2     implements org.apache.jasper.runtime.JspSourceDependent {
 3 
 4   public void _jspService(HttpServletRequest request, HttpServletResponse response)
 5         throws java.io.IOException, ServletException {
 6 
 7     //局部变量定义
 8     ...
 9 
10     try {
11       ...
12         
13       //返回HTML
14       out.write("<html>\r\n");
15       ......
16     } catch (Throwable t) {
17     } finally {
18     }
19   }
20 }

 

 

Hello_jsp继承了抽象类HttpJspBase并实现了_jspService方法,而HttpJspBase又继承了HttpServlet,HttpJspBase的service()方法在执行时会调用_jspService()方法,这样Jetty就可以处理JSP请求了。

下面来看看Jetty是如何将JSP转变成Servlet的,Jetty处理JSP请求的时序图如下所示:

 

其中JspServlet和JspServletWapper位于org.apache.jasper.servlet包中,所以Jetty是调用jasper来处理JSP的。下面是JspServletWrapper.service()方法的代码片段:

 

 1            /*
 2              * (1) Compile
 3              */
 4             if (options.getDevelopment() || firstTime ) {
 5                 synchronized (this) {
 6                     firstTime = false;
 7 
 8                     // The following sets reload to true, if necessary
 9                     ctxt.compile();
10                 }
11             } else {
12                 if (compileException != null) {
13                     // Throw cached compilation exception
14                     throw compileException;
15                 }
16             }
17 
18             /*
19              * (2) (Re)load servlet class file
20              */
21             getServlet();
22 
23             // If a page is to be precompiled only, return.
24             if (precompile) {
25                 return;
26             }
27 
28             /*
29              * (3) Service request
30              */
31             if (theServlet instanceof SingleThreadModel) {
32                // sync on the wrapper so that the freshness
33                // of the page is determined right before servicing
34                synchronized (this) {
35                    theServlet.service(request, response);
36                 }
37             } else {
38                 theServlet.service(request, response);
39             }

 

从上述代码可以清楚的看到JSP的处理流程:先将JSP编译成Servlet,再通过反射机制获取这个Servlet的实例,最后调用这个Servlet的service方法。

         至此,Servlet和JSP的工作流程就很清楚了,现在回过头来看看文章开始处提到的问题:为什么war包中的jsp文件找不到了?Jetty对war包的处理很简单,在Jetty启动时,将war解压出来(文件名随机生成,类似于:Jetty_0_0_0_0_8080_test.war____.hcx133),放在JETTY_HOME的work目录下,如果JETTY_HOME没有work目录,就会放在系统的temp目录下。所以对于war中本来存在的JSP文件,Jetty包404错误,原因应该就是解压出来的文件被删除了。

 

        [后记]问题重现了,果然是war包解压出来的文件被删了,linux会自动删除/tmp内超过10天没有更新的文件。

分类:

技术点:

相关文章:

  • 2021-12-08
  • 2021-09-08
  • 2021-09-06
  • 2022-02-15
  • 2022-02-10
猜你喜欢
  • 2022-01-10
  • 2021-08-27
  • 2021-04-15
  • 2021-06-08
  • 2021-12-19
  • 2021-08-06
相关资源
相似解决方案