Servlet工作原理(转)

Servlet运行在Servlet容器中,由容器负责Servlet实例的查找及创建工作,并按照Servlet规范的规定调用Servlet的一组方法,这些方法也叫生命周期的方法。具体调用过程如下图所示:

Servlet生命周期详解

如上图所示,Servlet的生命周期可以分为四个阶段,即装载类及创建实例阶段、初始化阶段、服务阶段和实例销毁阶段。下面针对每个阶段的编程任务及注意事项进行详细的说明。

(1)装载类及创建实例

客户端向Web服务器发送一个请求,请求的协议及路径必须遵守如下的格式:
    http://serverip:port/application-path/resource-path

其中,serverip为Web服务器的IP地址,也可以是域名,比如:192.168.0.1、202.196.152.115、 
    localhost、www.sina.com.cn等。port为Web服务器的服务端口,如果是80端口可以不写。
    application-path为服务器中发布的某个应用的路径,如果为缺省应用(比如tomcat的ROOT)可以为
    空。resource-path为客户端要访问的服务器中的资源的路径。比如:
        http://localhost:8080/serv-app/login.html 表示通过8080端口访问本地机器上名字为路径为 serv-app中/login.html对应的资源。 
    http://localhost:8080/serv-app/basic/time 表示通过8080端口访问本地机器上路径为serv-app的应用中/basic/time对应的资源。

那么Web服务器是如何解释该请求的路径,以及将资源发送给客户端呢?在前面的“建立并发布一个Web应用”部分,我们说过Web服务器会将应用的路径/serv-app映射到磁盘的某个特定的目录结构,本例中为tomcat服务器中webapps目录下的serv-app。/login.html和/basic/time为该应用下的资源的路径,该路径同应用路径一样为“虚拟的”路径,由服务器把它映射为系统的具体文件或程序,具体流程如下图所示:

JavaEE Web规范规定了服务器搜索Servlet类的路径为应用目录结构中WEB-INF/classes目录及WEB-INF/lib下的所有jar文件。因此需要将TimeServlet按照如下的目录结构放到WEB-INF/classes中:
WEB-INF/classes/com/allanlxf/servlet/basic/TimeServlet.class

该Servlet部署描述如下:

<servlet>
           <servlet-name>TimeServlet</servlet-name>
           <servlet-class>com.allanlxf.servlet.basic.TimeServlet</servlet-class>
         </servlet>
        <servlet-mapping>
           <servlet-name>TimeServlet</servlet-name>
           <url-pattern>/basic/time</url-pattern>
         </servlet-mapping>

I.何时创建Servlet实例?

在默认情况下Servlet实例是在第一个请求到来的时候创建,以后复用。如果有的Servlet需要复杂的操作需要载初始化时完成,比如打开文件、初始化网络连接等,可以通知服务器在启动的时候创建该Servlet的实例。具体配置如下:

<servlet>
      <servlet-name>TimeServlet</servlet-name>
      <servlet-class>com.allanlxf.servlet.basic.TimeServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
    
</servlet>

其中<load-on-startup>标记的值必须为数值类型,表示Servlet的装载顺序,取值及含义如下:
正数或零:该Servlet必须在应用启动时装载,容器必须保证数值小的Servlet先装载,如果多个
             Servlet的<load-on-startup>取值相同,由容器决定它们的装载顺序。
    负数或没有指定<load-on-startup>:由容器来决定装载的时机,通常为第一个请求到来时。

(2)初始化

一旦Servlet实例被创建,Web服务器会自动调用init(ServletConfig config)方法来初始化该Servlet。其中方法参数config中包含了Servlet的配置信息,比如初始化参数,该对象由服务器创建。

I.如何配置Servlet的初始化参数?

在web.xml中该Servlet的定义标记中,比如:

<servlet>
         <servlet-name>TimeServlet</servlet-name>
         <servlet-class>com.allanlxf.servlet.basic.TimeServlet</servlet-class>
        <init-param>
            <param-name>user</param-name>
            <param-value>allanlxf</param-value>
       
</init-param>
       
<init-param>
           <param-name>blog</param-name>
           <param-value>http://allanlxf.blog.sohu.com</param-value>
       
</init-param>
    </servlet>

配置了两个初始化参数user和blog它们的值分别为allanlxf和http://allanlxf.blog.sohu.com, 这样以后要修改用户名和博客的地址不需要修改Servlet代码,只需修改配置文件即可。

II.如何读取Servlet的初始化参数?

ServletConfig中定义了如下的方法用来读取初始化参数的信息:

public String getInitParameter(String name)

参数:初始化参数的名称。
          返回:初始化参数的值,如果没有配置,返回null。

比如:getInitParameter(“user”) 返回 allanlxf
                 getInitParameter(“blog”) 返回 http://allanlxf.blog.sohu.com

public java.util.Enumeration getInitParameterNames()

返回:该Servlet所配置的所有初始化参数名称的枚举。

III.init(ServletConfig)方法执行次数

在Servlet的生命周期中,该方法执行一次。

IV.init(ServletConfig)方法与线程

     该方法执行在单线程的环境下,因此开发者不用考虑线程安全的问题。

V.init(ServletConfig)方法与异常

   该方法在执行过程中可以抛出ServletException来通知Web服务器Servlet实例初始化失败。一旦ServletException抛出,Web服务器不会将客户端请求交给该Servlet实例来处理,而是报告初始化失败异常信息给客户端,该Servlet实例将被从内存中销毁。如果在来新的请求,Web服务器会创建新的Servlet实例,并执行新实例的初始化操作。

VI.配置初始化参数VS覆盖init(ServletConfig)方法

    配置初始化参数与覆盖init(ServletConfig)方法并没有必然的联系,这是很多初学者容易搞混的地方。配置初始化参数的目的是为了编写“通用”的Servlet,即通过改变初始化参数的值来改变Servlet的功能,而不必修改Servlet的源代码。覆盖init(ServletConfig)方法的原因是某些Servlet为客户提供服务需要执行一次性的操作,比如申请资源、打开文件、建立网络连接等,这些操作要么比较耗时,要么这些资源是提供服务的必要条件。

(3)服务

一旦Servlet实例成功创建及初始化,该Servlet实例就可以被服务器用来服务于客户端的请求并生成响应。在服务阶段Web服务器会调用该实例的service(ServletRequest request, ServletResponse response)方法,request对象和response对象有服务器创建并传给Servlet实例。request对象封装了客户端发往服务器端的信息,response对象封装了服务器发往客户端的信息。

I. service()方法的职责

service()方法为Servlet的核心方法,客户端的业务逻辑应该在该方法内执行,典型的服务方法的开发流程为:

解析客户端请求-〉执行业务逻辑-〉输出响应页面到客户端

II.service()方法与线程

为了提高效率,Servlet规范要求一个Servlet实例必须能够同时服务于多个客户端请求,即service()方法运行在多线程的环境下,Servlet开发者必须保证该方法的线程安全性。

III.service()方法与异常

service()方法在执行的过程中可以抛出ServletException和IOException。其中ServletException可以在处理客户端请求的过程中抛出,比如请求的资源不可用、数据库不可用等。一旦该异常抛出,容器必须回收请求对象,并报告客户端该异常信息。IOException表示输入输出的错误,编程者不必关心该异常,直接由容器报告给客户端即可。

IV.编写线程安全的资源

由于Servlet实例的service()方法在同一时刻会运行到多线程的环境下,因此,编写Servlet不得不考虑的因素就是线程安全的问题,这也是编写Servlet最容易出错的地方。下面对Servlet的方法和线程之间的关系以及编程的原则进行详细的说明。 

编程注意事项说明:

1) 当Server Thread线程执行Servlet实例的init()方法时,所有的Client Service Thread线程都不能执行该实例的service()方法,更没有线程能够执行该实例的destroy()方法,因此Servlet的init()方法是工作在单线程的环境下,开发者不必考虑任何线程安全的问题。

2) 当服务器接收到来自客户端的多个请求时,服务器会在单独的Client Service Thread线程中执行Servlet实例的service()方法服务于每个客户端。此时会有多个线程同时执行同一个Servlet实例的service()方法,因此必须考虑线程安全的问题。

3) 请大家注意,虽然service()方法运行在多线程的环境下,并不一定要同步该方法。而是要看这个方法在执行过程中访问的资源类型及对资源的访问方式。分析如下:

i. 如果service()方法没有访问Servlet的成员变量也没有访问全局的资源比如静态变量、文件、数据库连接等,而是只使用了当前线程自己的资源,比如非指向全局资源的临时变量、request和response对象等。该方法本身就是线程安全的,不必进行任何的同步控制。

ii. 如果service()方法访问了Servlet的成员变量,但是对该变量的操作是只读操作,该方法本身就是线程安全的,不必进行任何的同步控制。

iii. 如果service()方法访问了Servlet的成员变量,并且对该变量的操作既有读又有写,通常需要加上同步控制语句。

iv. 如果service()方法访问了全局的静态变量,如果同一时刻系统中也可能有其它线程访问该静态变量,如果既有读也有写的操作,通常需要加上同步控制语句。

v. 如果service()方法访问了全局的资源,比如文件、数据库连接等,通常需要加上同步控制语句。

V. 关于SingleThreadModel

    在默认的情况下,Web服务器会为web.xml中每个<servlet>标签声明的Servlet创建Servlet唯一一个的实例,运行是会将该实例交给多个线程处理并发的客户端请求。因此Servlet的开发者必须保证Servlet的线程安全性。

Servlet规范中也规定了一个SingleThreadModel接口,该接口为标记型接口,没有任何方法,目的在于告诉容器该类型的Servlet的工作方式。只要Servlet类实现了该接口,Web服务器必须保证该类型的Servlet的实例在同一时刻只能服务于某一个请求,即service()方法不在并发的线程中。

注意,该运行方式只保证了Servlet实例的成员属性工作在单线程的环境下,但被Servlet访问的其它资源,比如HttpSession、文件、网络连接等也有可能同时被其它的Servlet实例访问,因此该运行方式并不能彻底解决线程并发的问题,建议开发者慎重使用。

(4)销毁

当Web服务器认为Servlet实例没有存在的必要了,比如应用重新装载,或服务器关闭,以及Servlet很长时间都没有被访问过。服务器可以从内存中销毁(也叫卸载)该实例。Web服务器必须保证在卸载Servlet实例之前调用该实例的destroy()方法,以便回收Servlet申请的资源或进行其它的重要的处理。

I. destroy()与service()
     Web服务器必须保证调用destroy()方法之前,让所有正在运行在该实例的service()方法中的线程退出或者等待这些线程一段时间。一旦destroy()方法已经执行,Web服务器将拒绝所有的新到来的对该Servlet实例的请求,destroy()方法退出,该Servlet实例即可以被垃圾回收。

生命周期应用实例

编写一个Servlet,该Servlet记录实例创建以来所有访问过该实例的客户端的IP地址到服务器的某个日志文件中。该日志文件的路径必可以在部署Servlet的时候由部署者指定。

代码: 

package com.allanlxf.servlet.lifecycle;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

/**
    * Servelt工作原理实例,将所有访问过该客户端的IP地址记录到服务器的某个文件中。
    * 
    * @author AllanLxf
    * @version 1.0
    */
public class IPLogServlet extends HttpServlet
{
        private PrintWriter logger;

/**
          * Servlet的初始化方法。 不同的平台下文件的路径写法不一样,为了做到Servlet的平台无
         * 关性,将用来保存客户端IP地址的文件路径以初始化参数的形式提供,这样只需在部署Servlet
         * 的时候指定或改变该文件的 路径即可,而不用重新编译Servlet源代码。
         * 由于频繁打开关闭文件效率很低,所以在init()方法中打开文件,在service()方法中写文
         * 件,在destroy()方法中关闭文件为最佳实践。
          * @throws ServletException
          *     如果Servlet没有配置初始化参数filename或参数filename所指定的文件无法找到时。
          */
         public void init() throws ServletException
         {
             String filename = getInitParameter("filename");
             try
             {
                 FileOutputStream fout = new FileOutputStream(filename, true);
                 logger = new PrintWriter(fout);
             } catch (IOException e)
             {
                 throw new ServletException("fail to open:" + filename);
             }
         }

/**
          * Servlet服务方法,记录客户端的IP地址到日志文件中。
          * 由于文件属于共享资源,多个线程同时写一个文件,会出现结果混乱的情况,所以要控制同步
         * 访问。
          * @param request     包含客户端请求信息的对象。
          * @param response    包含服务器响应信息的对象。
          */
        public void service(HttpServletRequest request, HttpServletResponse response)
                                             throws ServletException, IOException
        {
             response.setContentType("text/html;charset=gbk");
             PrintWriter out = response.getWriter();
             out.println("<html>");
             out.println("<head>");
             out.println(" <title>ip-log</title>");
             out.println("</head>");
             out.println("<body>");
             out.println("<h3 align=\"center\">Thanks for your visiting!</h3>");
             out.println("</body>");
             out.println("</html>");
             //记录日志信息

synchronized (this)
             {
                 logger.print(new java.util.Date());
                 logger.print(":来自客户端:");
                 logger.println(request.getRemoteAddr());
             }
        }

/**
          * Servlet实例销毁方法。关闭日志文件。
          */
        public void destroy()
        {
             logger.close();
        }
}

Servlet与URL匹配

为了让客户端访问服务器中的Servlet,部署者需要为每个Servlet配置一个访问路径,该路径有如下的三种写法:

(1)确切路径匹配
     以“/”开始,后面跟一个具体的路径名称,也可以包含子路径。比如:/time、/basic/time、/basic/time/http都属确切的路径匹配。在该匹配模式下,客户端只能通过这一唯一的路径来访问该Servlet实例。

(2)模糊路径匹配
      以“/”开始,以“/*”结束,中间可以包含子路径。比如:/*、/basic/*、/user/management/* 都属于模糊路径匹配。在该匹配模式下,客户端可以通过一组相关的路径来访问该Servlet的实例,即可以通过URL来传递附加信息。

(3)扩展名匹配
      以”*.”开始,以任意其它的字符结束。比如:*.do、*.action、*.ctrl等都属于扩展名的匹配。在该匹配模式下,客户端可以通过一组相关的路径来访问该Servlet的实例,即可以通过URL来传递附加信息。

(4)缺省的Servlet
      配置成“/”的Servlet为该应用的缺省的Servlet,Web服务器会将所有的无法识别的客户端请求交给缺省的Servlet来处理。

匹配优先级别

在一个Web应用中会同时发布多个Servlet,不可避免的会出现多个Servlet都可以服务于某一个请求的情况。比如系统中发布了三个Servlet,它们的匹配路径分别为:/* 、 *.do 及 /basic,如果客户端的请求路径位:/basic,那么 /basic以及 /* 都可以服务于该请求。因此规范中规定了Web服务器匹配Servlet的顺序规则,具体顺序规定如下:

1. 寻找确切的路径匹配的Servlet。

2. 如果没有确切的路径匹配,按照模糊的路径匹配,如果有多个路径存在,取固定部分路径最长的Servlet。

3. 寻找扩展名的匹配。

4. 如果上述规则都无法匹配到Servlet,系统会将请求交给缺省的Servlet处理。

5. 如果没有缺省的Servlet,报告错误信息给调用者。

假如系统中有如下的Servlet的匹配模式: 
    

下面分别用不同的路径访问该应用,匹配结果如下: 
                  

时间: 2024-10-12 06:19:32

Servlet工作原理(转)的相关文章

JSP/Servlet 工作原理

JSP/Servlet 工作原理(转载) 2014-03-08 23:20 1829人阅读 评论(0) 收藏 举报 Servlet Servlet 没有 main 方法,不能够独立的运行,它的运行需要容器的支持,Tomcat 是最常用的 JSP/Servlet 容器. Servlet 运行在 Servlet 容器中,并由容器管理从创建到销毁的整个过程. 对于用户到达Servlet的请求,Servlet容器会创建特定于这个请求的ServletRequest对象和 ServletResponse对象

Servlet 工作原理解析

-----转自许令波老师Servlet 工作原理解析  感觉写的很不错,保存下来,留着以后温习 从 Servlet 容器说起 要介绍 Servlet 必须要先把 Servlet 容器说清楚,Servlet 与 Servlet 容器的关系有点像枪和子弹的关系,枪是为子弹而生,而子弹又让枪有了杀伤力.虽然它们是彼此依存的,但是又相互独立发展,这一切都是为了适应工业化生产的结果.从技术角度来说是为了解耦,通过标准化接口来相互协作.既然接口是连接 Servlet 与 Servlet 容器的关键,那我们就

Servlet工作原理

Servlet生命周期分为三个阶段: 1,初始化阶段  调用init()方法 2,响应客户请求阶段 调用service()方法 3,终止阶段 调用destroy()方法 Servlet初始化阶段: 在下列时刻Servlet容器装载Servlet: 1,Servlet容器启动时自动装载某些Servlet,实现它只需要在web.XML文件中的<Servlet></Servlet>之间添加如下代码: <loadon-startup>1</loadon-startup&g

【Java】Servlet 工作原理解析

Web 技术成为当今主流的互联网 Web 应用技术之一,而 Servlet 是 Java Web 技术的核心基础.因而掌握 Servlet 的工作原理是成为一名合格的 Java Web 技术开发人员的基本要求.本文将带你认识 Java Web 技术是如何基于 Servlet 工作,你将知道:以 Tomcat 为例了解 Servlet 容器是如何工作的?一个 Web 工程在 Servlet 容器中是如何启动的? Servlet 容器如何解析你在 web.xml 中定义的 Servlet ?用户的请

Servlet工作原理分析

最近在看<Java Web技术内幕>的Servlet工作原理,有点深奥和难以理解,于是乎,想通过一个简单的Demo将书上的思路理一遍,对Servlet有个更透彻更深的了解. Servlet类:HelloWorld.java package com.cqupt; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletReq

走进JavaWeb技术世界4:Servlet 工作原理详解

本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下Star哈 文章首发于我的个人博客: www.how2playlife.com 本文是微信公众号[Java技术江湖]的<走进JavaWeb技术世界>其中一篇,本文部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客内容,引用其中了一些比较好的博客文章,如有侵权,请联系作

【Tomcat】Servlet 工作原理解析

Web 技术成为当今主流的互联网 Web 应用技术之一,而 Servlet 是 Java Web 技术的核心基础.因而掌握 Servlet 的工作原理是成为一名合格的 Java Web 技术开发人员的基本要求.本文将带你认识 Java Web 技术是如何基于 Servlet 工作,你将知道:以 Tomcat 为例了解 Servlet 容器是如何工作的?一个 Web 工程在 Servlet 容器中是如何启动的? Servlet 容器如何解析你在 web.xml 中定义的 Servlet ?用户的请

Servlet工作原理(读许令波《深入分析javaWeb技术内幕》)笔记

在介绍servlet的工作原理之前首先我们要先了解一下与servlet配套的servlet容器,本文以tomcat为例 1.Tomcat容器的基础知识 Tomcat的容器是分级管理共分为四个等级从上之下为:container,engine,host,context. 而直接管理servlet的容器是context容器.在tomcat中一个context容器对应一个web app应用 2.servlet容器的启动过程 当在Tomcat中添加一个应用的时候Tomcat会创建一个standardCon

Java web每天学之Servlet工作原理详情解析

上篇文章中我们介绍了Servlet的实现方式以及Servlet的生命周期,我们这篇文章就来介绍一下常用对象. 点击回顾:<Java Web每天学之Servlet的工作原理解析>:<Java Web每天学之Servlet的工作原理解析(二)> 一.HttpServletRequest对象 1.介绍HttpServletRequest对象:主要作用是用来接收客户端发送过来的请求信息,例如:请求的参数,发送的头信息等都属于客户端发来的信息,service()方法中形参接收的是HttpSe