Servlet基础

Servlet

一. Servlet 概述

我们日常所接触到的应用有很大一部分都是基于 请求/响应架构 的,如下图所示。在这种架构中,一般由两个角色组成,即: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()方法用于处理并响应请求。

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

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的执行流程

(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对象的有关方法,生成响应数据;

五. Servlet 与 并发
1、Servlet容器如何同时来处理多个请求

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

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值