Java Web基础 --- Servlet 综述(理论篇)

摘要:

  Web 技术成为当今主流的互联网 Web 应用技术之一,而 Servlet 是 Java Web 技术的核心基础。本文首先从请求/响应架构应用的大背景谈起 Servlet 的由来,明确 Servlet 的产生动机,并揭示了 Servlet 的本质以及其在标准MVC模式中所扮演的角色。紧接着,给出了 Servlet族的继承结构,并对族内的接口和抽象类作了进一步的介绍,并给出开发一个Servlet程序的常用做法。在此基础上,我们图文并茂地介绍了 Servlet 的生命周期与执行流程,清晰展现 Servlet 的原理。特别地,由于Servlet容器默认采用单实例多线程的方式来处理请求,我们进一步介绍了Servlet 与并发的联系,并就Servlet容器如何同时来处理多个请求和如何开发线程安全的Servlet两个问题进行讨论和总结。最后,结合Java Web 应用的结构演变历程,给出了MVC架构的基本组成要素、内在联系和作用分工,并给出了 Servlet 在其中所扮演的角色。

  本篇主要介绍 Servlet 理论方面的知识,更多关注于以下几个问题:

  • 为什么会有 Servlet;
  • Servlet 是什么;
  • Servlet 如何实现预期效果;
  • Servlet 的作用原理;
  • Servlet 与 并发;
  • Servlet 与 Java Web 应用的结构演变历程;
  • Servlet 与 MVC 的联系。


  更多关于Servlet使用、实践方面的介绍以及Servlet新特性(Servlet 异步处理、Servlet 非阻塞IO 以及 Servlet 文件上传等)的总结见本文的姊妹篇《Servlet 综述(实践篇)》。 
   
  更多关于Java并发相关知识请移步我的专栏《Java并发编程学习笔记》。该专栏全面记录了Java并发编程的相关知识,并结合操作系统、Java内存模型和相关源码对并发编程的原理、技术、设计、底层实现进行深入分析和总结,并持续跟进并发相关技术。



版权声明:

本文原创作者:书呆子Rico 
作者博客地址:http://blog.csdn.net/justloveyou_/


一. Servlet 概述

1、从请求/响应架构谈 Servlet 的由来

  我们之前在博文《Java Web基础 — Jsp 综述(下)》中对 请求/响应架构 有过比较系统的叙述,现在我们作进一步叙述。

  我们日常所接触到的应用有很大一部分都是基于 请求/响应架构 的,如下图所示。在这种架构中,一般由两个角色组成,即:Server 和 User Agent。特别地,根据 User Agent 的不同,我们可以将应用分为 B/S模式(User Agent 为浏览器时) 和 C/S模式。但无论哪种模式,Server 与 User Agent 的交互使用的都是同一个请求和应答的标准,即 HTTP 协议

  一般地,以浏览器为例,User Agent 的作用就是根据用户的请求URL生成相应的 HTTP请求报文发送给服务器,并对服务器的响应进行解析(或渲染),使用户看到一个丰富多彩的页面。但是,如果我们需要在网页上完成一些业务逻辑(比如,登陆验证),或者需要从服务器的数据库中取一些数据作为网页的显示内容,那么除了负责显示的HTML标记之外,必须还要有完成这些业务功能的代码存在,这种网页我们称之为 动态网页

  对于静态网页而言,服务器上存在的是一个个纯HTML文件。当客户端浏览器发出HTTP请求时,服务器可以根据请求的URL找到对应的HTML文件,并将HTML代码返回给客户端浏览器。但是对于动态网页,服务器上除了找到需要显示的HTML标记外,还必须执行所需要的业务逻辑,然后将业务逻辑运算后的结果和需要显示的HTML标记一起生成新的HTML代码。最后,将新的带有业务逻辑运算结果的HTML代码返回给客户端。为了实现动态网页的目标,Servlet技术(利用输出流动态生成 HTML 页面)应运而生,它能够以一种可移植的方法来提供动态的、面向用户的内容。

            



2、Servlet 的本质与角色

  Web 技术成为当今主流的互联网 Web 应用技术之一,而 Servlet 是 Java Web 技术的核心基础。包括我们在前面的博文中谈到的JSP,也只是为了弥补使用 Servlet 作为表现层的不足而提出的。JSP规范通过实现普通静态HTML和动态部分的混合编码,使得逻辑内容与外观相分离,大大简化了表示层的实现。但是,JSP并没有增加任何本质上不能用Servlet实现的功能,只是在JSP中编写静态HTML更加方便。事实上,JSP的本质仍然是Servlet,并且站在表现层的角度上来看,JSP 是 Servlet 的一种就简化。

  Servlet 是 J2EE 标准的一部分,是一种运行在Web服务器端的小型Java程序,更具体的说,Servlet 是按照Servlet规范编写的一个Java类,用于交互式地浏览和修改数据,生成动态Web内容。要注意的是,由于 Servlet 是服务器端小程序,所以 Servlet 必须部署在 Servlet 容器中才能使用,例如 Tomcat,Jetty 等。

  在标准的MVC模式中,Servlet 仅作为控制器使用,而控制器角色的作用是:负责接收客户端的请求,它既不直接对客户端输出响应,也不处理用户请求,只是调用业务逻辑组件(JavaBean)来处理用户请求。一旦业务逻辑组件处理结束后,控制器会根据处理结果,调用不同的表现层页面向浏览器呈现处理结果。


二. Servlet API

  关于 Servlet 的接口主要在以下两个包中,Servlet 继承结构如下图所示:

  • javax.servlet.* :存放与HTTP 协议无关的一般性Servlet 类;
  • javax.servlet.http.* :除了继承 javax.servlet.* 之外,还增加了与HTTP协议有关的功能。

      特别需要说明的是,所有的 Servlet 都必须实现 javax.servlet.Servlet 接口。若我们开发的 Servlet程序与HTTP协议无关,那么可以直接继承 javax.servlet.GenericServlet抽象类 ;否则,若我们开发的Servlet程序和HTTP协议有关,那么可以直接继承 javax.servlet.http.HttpServlet 抽象类。

                              



  下面我们分别看一下 Servlet接口、ServletConfig接口、GenericServlet抽象类 和 HttpServlet抽象类给我们提供的接口:

(1) Interface Servlet

             

  Servlet 接口定义了所有Servlet都必须实现的方法。其中,destroy()方法、init()方法 和 service()方法 由Servlet容器来调用。特别地,service()方法用于处理并响应请求。



(2) Interface ServletConfig

  A servlet configuration object used by a servlet container to pass information to a servlet during initialization.

             



(3) Abstract Class GenericServlet

  GenericServlet implements the Servlet and ServletConfig interfaces. GenericServlet may be directly extended by a servlet, although it’s more common to extend a protocol-specific subclass such as HttpServlet.

  GenericServlet makes writing servlets easier. It provides simple versions of the lifecycle methods init and destroy and of the methods in the ServletConfig interface. GenericServlet also implements the log method, declared in the ServletContext interface.

  To write a generic servlet, you need only override the abstract service method.

             



(4) Abstract Class HttpServlet

             

  通过继承 HttpServlet 可以很方便的帮助我们创建一个 HTTP Servlet。我们可以看到,HttpServlet 为各种Http请求都提供了相应的处理方式。但是,我们从Servlet接口中我们了解到,service()方法用于生成对客户端的响应,那么 service()方法与图中所示的七种Http请求处理方法有什么联系呢?下面请看 HttpServlet 对service()方法的实现:

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
       }
    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
    } else {
        // Error
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

  我们知道,service()方法用于生成对客户端的响应。但对于 HTTPServlet 而言,service()方法会按照具体的请求类型将请求进一步分发到对应的处理方法进行处理。由于在大部分时候,Servlet 对各种类型的请求的处理方式都是一样的,因此我们只需重写service()方法即可响应客户端的所有请求。或者,另一种处理方式是,由于客户端的请求通常只有 GET 和 POST 两种,因此我们只需重写doGet() 和 doPost()两个方法即可。

  实际上,普通Servlet类里的service()方法的作用,完全等同于JSP转译所生成Servlet类里的_jspService()方法。


三. Servlet的生命周期与执行流程

1、Servlet的生命周期

  当 Servlet 在容器中运行时,其实例的创建及销毁等都不是由程序员所决定的,而是由Web容器进行控制的。一个Servlet对象的创建有两个时机:用户请求之时或应用启动之时。

 (1) 客户端第一次请求某个Servlet时,容器创建该Servlet实例,这也是大部分Servlet创建实例的时机; 
 (2) Web应用启动时立即创建Servlet实例,即 load-on-startup Servlet。 

  但每个Servlet都遵循一个生命周期,即:Servlet 实例化–>Servlet 初始化—>服务—>销毁。具体流程如下图所示:

  • 创建Servlet实例;
  • Web容器调用Servlet的init()方法,对Servlet进行初始化。特别地,在Servlet的生命周期中,仅执行一次init()方法;
  • Servlet 被初始化后,将一直存在于Web容器中,用于响应客户端请求。默认的请求处理、响应方式是调用与HTTP请求的方法相应的do方法;
  • Web容器决定销毁Servlet时,先调用Servlet的 destroy() 的方法回收资源,通常在关闭Web应用之时销毁 Servlet。和 init() 方法类似,destroy()方法在Servlet的生命周期中也仅执行一次。当Servlet对象退出生命周期时,负责释放占用的资源。一个Servlet在运行service()方法时可能会产生其他的线程,因此需要确保在调用destroy()方法时,这些线程已经终止或完成。

              

      Ps:本图来源于CSDN博友何静媛JAVA学习篇–Servlet详解一文。



2、Servlet的执行流程

 Servlet的执行流程如下图所示:

  (1) User Agent 向 Servlet容器(Tomcat)发出Http请求; 
  (2) Servle容器接收 User Agent 发来的请求; 
  (3) Servle容器根据web.xml文件中Servlet相关配置信息,将请求转发到相应的Servlet; 
  (4) Servlet容器创建一个 HttpServlet对象,用于处理请求; 
  (5) Servlet容器创建一个 HttpServletRequest对象,将请求信息封装到这个对象中; 
  (6) Servlet容器创建一个HttpServletResponse对象; 
  (7) Servlet容器调用HttpServlet对象的service方法,并把HttpServltRequest对象与HttpServltResponse对象作为参数传给 HttpServlet 对象; 
  (8) HttpServlet调用HttpServletRequest对象的有关方法,获取Http请求信息; 
  (9) HttpServlet调用JavaBean对象(业务逻辑组件)处理Http请求; 
  (10) HttpServlet调用HttpServletResponse对象的有关方法,生成响应数据; 
  (11) Servlet容器将HttpServlet的响应结果传给 User Agent。

             



3、load-on-servlet

  load-on-servlet 指的是应用启动时就创建的Servlet,这些Servlet通常是用于后台服务的Servlet或者需要拦截很多请求的Servlet。也就是说,这些Servlet常常作为应用的基础Servlet使用,提供重要的后台服务。例如:

@WebServlet(loadOnStartup=1)
public class TimerServlet extends HttpServlet
{
    public void init(ServletConfig config)throws ServletException
    {
        super.init(config);
        Timer t = new Timer(1000,new ActionListener()       // 匿名内部类
        {
            public void actionPerformed(ActionEvent e)
            {
                System.out.println(new Date());
            }
        });
        t.start();
    }
}

  我们看到,这个 load-on-servlet 没有提供 service()方法,这表明它不能响应用户请求,所以无需为它配置URL映射。由于它不能接收用户请求,所以只能在应用启动时实例化。

  特别需要注意的是,loadOnStartup属性用于标记容器是否在启动的时候加载这个servlet。当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。其中,正数值越小,启动该servlet的优先级越高。


四. Servlet的配置

  为了让Servlet能够响应用户请求,必须将Servlet配置到Web应用中。从 J2EE 6(即 Servlet 3.0)开始,配置Servlet有两种方式,即 @WebServlet注解配置 和 传统的 web.xml 配置,这部分内容在我的下一篇博客《Servlet 综述(实践篇)》有详细介绍,此不赘述。


五. Servlet 与 并发

1、Servlet容器如何同时来处理多个请求

  Servlet 采用多线程来处理多个请求同时访问。更具体地,Servlet 依赖于一个线程池来服务请求,所谓线程池实际上是一系列的工作者线程集合,该集合包含的是一组等待执行任务的线程。此外,Servlet 使用一个调度线程来管理这些工作者线程。

  当Servlet容器收到一个Servlet请求时,调度线程会从线程池中选出一个工作者线程,并将请求传递给该工作者线程,然后由该线程来执行Servlet的service()方法。当这个线程正在执行的时候,如果容器收到另外一个请求,调度线程将同样从线程池中选出另一个工作者线程来服务新的请求,特别需要注意的是,容器并不关心这个请求是否访问的是同一个Servlet。当容器同时收到对同一个Servlet的多个请求时,那么这个Servlet的service()方法将在多线程中并发执行。

  Servlet容器默认采用单实例多线程的方式来处理请求,这样减少产生Servlet实例的开销,提升了对请求的响应时间,对于Tomcat容器,我们可以在其server.xml中通过元素设置线程池中的线程数目。



2、如何开发线程安全的Servlet

  Servlet容器采用多线程来处理请求,提高性能的同时也造成了线程安全问题。要开发线程安全的 Servlet应该从这几个方面进行:

(1). 变量的线程安全: 多线程并不共享局部变量,所以我们要尽可能的在Servlet中使用局部变量;

(2). 代码块的线程安全: 可以使用Synchronized、Lock 和 原子操作(java.util.concurrent.atomic)来保证多线程对共享变量的协同访问;但是要注意的是,要尽可能得缩小同步代码的范围,尽量不要在service方法和响应方法上直接使用同步,这会严重影响性能;

(3). 属性的线程安全: ServletContext,HttpSession,ServletRequest对象中属性是线程安全的;

(4). 使用线程安全容器: 使用 java.util.concurrent 包下的线程安全容器代替 ArrayList、HashMap 等非线程安全容器;



  更多关于Java并发相关知识请移步我的专栏《 Java并发编程学习笔记》。该专栏全面记录了Java并发编程的相关知识,并结合操作系统、Java内存模型和相关源码对并发编程的原理、技术、设计、底层实现进行深入分析和总结,并持续跟进并发相关技术。


六. Servlet 与 MVC

  Java Web 应用的结构经历了 Model1 和 Model2 两个时代。在 Model1 模式下,整个 Web 应用几乎全部用JSP页面组成,只用少量的JavaBean来处理数据库连接、访问等操作。从工程化角度来看,JSP 不但充当了表现层角色,还充当了控制器角色,将控制逻辑和表现逻辑混杂在一起,导致代码重用率极低,使得应用极难扩展和维护。

  Model2 已经是基于MVC架构的设计模式。在 Model2 中,Servlet 作为前端控制器,负责接收客户端发送的请求,在 Servlet 中只包含控制逻辑和简单的前端处理;然后,Servlet 调用后端的JavaBean(业务逻辑组件)来处理业务逻辑;最后,根据处理结果转发到相应的JSP页面处理显示逻辑。在 Model2 模式下,模型(Model)由 JavaBean 充当,视图(View)由JSP页面充当,而控制器则由 Servlet 充当。Model2 的流程示意图如下:

              

  更具体地,在 Model2(标准MVC)中,角色分工如下:

  • Model:由 JavaBean 充当,所有的业务逻辑、数据库访问都在Model中实现;
  • View:由 JSP 充当,负责收集用户请求参数并将应用处理结果、状态数据呈现给用户;
  • Controller:由 Servlet 充当,作用类似于调度员,即所有用户请求都发送给 Servlet,Servlet 调用 Model 来处理用户请求,并根据处理结果调用 JSP 来呈现结果;或者Servlet直接调用JSP将应用处理结果展现给用户。

引用

《轻量级 JavaEE 企业应用实战(第四版)》 
web.xml中load-on-startup的作用 
JAVA学习篇–Servlet详解 
Servlet 生命周期、工作原理 
Servlet总结 
Servlet容器如何同时来处理多个请求

原文地址:https://www.cnblogs.com/daxiong225/p/9314547.html

时间: 2024-10-27 08:56:16

Java Web基础 --- Servlet 综述(理论篇)的相关文章

Java Web基础 --- Servlet 综述(实践篇)

摘要: 伴随 J2EE 6一起发布的Servlet 3.0规范是Servlet规范历史上最重要的变革之一,它的许多新的特性都极大的简化了 Java Web 应用的开发.本文从一个简单的 Servlet 例子开始,说明了如何开发.配置一个 Servlet.此外,还重点叙述了Servlet的一些新特性,包括Servlet 异步处理.Servlet 非阻塞IO 以及 Servlet 文件上传等内容,以便我们对Servlet有一个更全面的了解. 本篇主要介绍 Servlet 实践方面的知识,更多关注于S

Java Web基础 --- Jsp 综述(上)

摘要: J2EE是一套规范,而Servlet/Jsp是J2EE规范的一部分,是Tomcat的主要实现部分.在最初的应用实践中,当用户向指定Servlet发送请求时,Servlet利用输出流动态生成HTML页面,这导致Servlet开发效率极为低下.JSP技术通过实现普通静态HTML和动态部分混合编码,使得逻辑内容与外观相分离,大大简化了表示层的实现,提高了开发效率.本文以JSP的本质是Servlet为主线,结合JSP转译后所得的Servlet,详细探讨了JSP的原理.执行过程.脚本元素.编译指令

Java Web基础 --- Jsp 综述(下)

摘要: JSP脚本中包含九个内置对象,它们都是Servlet-API接口的实例,并且JSP规范对它们进行了默认初始化.本文首先通过一个JSP实例来认识JSP内置对象的实质,紧接着以基于请求/响应架构应用的运行机制为背景,引出JSP/Servlet的通信方式与内置对象的作用域,并对每个内置对象的常见用法进行深入介绍和总结. 一. JSP 九大内置对象概述及相关概念说明 JSP脚本中包含九个内置对象,这九个内置对象都是 Servlet API 接口的实例,并且JSP规范对它们进行了默认初始化(由 J

Java Web 基础 --- Filter 综述

摘要: 伴随J2EE一起发布的Servlet规范中还包括一个重要的组件--过滤器(Filter).过滤器可以认为是Servlet的一种加强版,它主要用于对用户请求进行预处理以及对服务器响应进行后处理,是个典型的处理链.Servlet规范使用了三个接口对过滤器进行了抽象,即Filter是对具体过滤器的抽象,FilterChain是基于AOP理念对责任链方面的抽象,FilterConfig则是对Filter配置的抽象.本文概述了Filter的提出动机.工作原理.使用流程和应用实例,并指出Java W

Java Web基础 - Servlet

Java Web基础学习---Servlet 1.什么是servlet jsp的前生就是servlet,servlet就是在服务器端上运行的小程序,一个servlet就是一个java类,并且可以通过"请求-响应",编程模型来访问这个驻留在服务器内存里的servlet程序 2. 手工编写第一个Servlet Servlet层级关系: Servlet(接口)-->GenericServlet(抽象类,与协议无关的Servlet)-->HttpServlet(抽象类,实现了Htt

Java web基础总结四之—— Servlet基础

Java web基础总结四之-- Servlet基础 一.什么是Servlet? 通过名字就能看出来,Servlet 就是在服务器上运行的小程序.Servlet是sun公司(现在已经属于oracle了)实现的一门用于开发动态java web资源的技术.Sun公司在其API中提供了一个servlet接口,如果你想开发一个动态的java web资源,需要完成以下2个步骤:编写一个Java类,实现servlet接口.把开发好的Java类部署到web服务器中. Servlet接口已经有了两个默认的实现类

Java Web基础(HTML、Servlet/JSP)

要成为牛逼的JavaWeb程序员, Java Web的基础非常重要,现在有各种成熟的设计框架例如JQuery.Spring.Struts.Mybatis,将Java Web基础的复杂且通用的逻辑进行封装,减少了程序员的代码量,提高了编码效率,但是这些框架非常不利于我们了解Java Web底层如何运作,甚至不知道HTML(HyperText MarkupLanguage).HTTP(HyperText Transfer Protocol).URL(Uniform Resource Locator)

Java Web基础知识之Filter:过滤一切你不想看到的事情

不要相信客户端, 所以做后端的人都应该铭记的事情.因为前端传过来的数据并不总是合法和有效的,所以后端是对访问资源的最后一道保护伞.之前我们在Spring中说到过AOP编程,AOP基础知识,它就可以在执行我们的方法之前进行一些预处理和验证来保护后端的资源.不难想到她的实现方式和本篇要说的过滤器的实现原理应该是相同的,都是通过Java的动态代理实现的(自己的理解). 在Java Web的开发中,过滤器用于拦截请求,并对ServletRequest对象进行处理,我们可以想到的,它可以用来验证权限.加密

从Java Web 基础看SSH架构

Java Web开发现在已然成为大型Wed项目的标准,之前一直盲目的使用框架,往往知其然不知其所以然.在经过一段时间的学习与开发,大概掌握了其脉络的基础上,对其做一定总结. 一.Java Web 基础 一个典型的Java Web项目往往包含这些元素:Jsp页面.Servlet.Listener.Filter,以及配置文件web.xml.其中: Jsp和Servlet基本是一回事,主要用来响应客户端的请求.当然Jsp中可以直接嵌入HTML标签,主要还是负责展现. Listener则是负责监听Web