Servlet的本质,它是如何工作的

Servlet介绍

Servlet(Server Applet),全称Java Servlet,未有中文译文。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。

Servlet运行于支持Java的应用服务器中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。

Servlet编程需要使用到javax.servlet 和 javax.servlet.http两个包下面的类和接口,在所有的类和接口中,javax.servlet.servlet 接口最为重要。所有的servlet程序都必须实现该接口或者继承实现了该接口的类。

Servlet是sun公司提供的一门用于开发动态web资源的技术。

Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:

编写一个Java类,实现servlet接口。

把开发好的Java类部署到web服务器中。

servlet接口定义的是一套处理网络请求的规范,所有实现servlet的类,都需要实现它那五个方法,其中最主要的是两个生命周期方法 init()和destroy(),还有一个处理请求的service(),也就是说,所有实现servlet接口的类,或者说,所有想要处理网络请求的类,都需要回答这三个问题:

init()你初始化时要做什么

destory()你销毁时要做什么

service()你接受到请求时要做什么

这是Java给的一种规范!

servlet是一个规范,那实现了servlet的类,就能处理请求了吗?

答案是,不能。

那请求怎么来到servlet呢?答案是servlet容器,比如我们最常用的tomcat,同样,你可以随便谷歌一个servlet的hello world教程,里面肯定会让你把servlet部署到一个容器中,不然你的servlet压根不会起作用。

tomcat才是与客户端直接打交道的家伙,他监听了端口,请求过来后,根据url等信息,确定要将请求交给哪个servlet去处理,然后调用那个servlet的service方法,service方法返回一个response对象,tomcat再把这个response返回给客户端。

Servlet的运行过程

Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:

①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。

②装载并创建该Servlet的一个实例对象。

③调用Servlet实例对象的init()方法。

④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。

⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。

Servlet接口实现类

Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。

HttpServlet指能够处理HTTP请求的servlet,它在原有Servlet接口上添加了一些与HTTP协议处理方法,它比Servlet接口的功能更为强大。因此开发人员在编写Servlet时,通常应继承这个类,而避免直接去实现Servlet接口。

HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。

在编写Servlet类的时候要继承javax.servlet.http.HttpServlet类。

如果是post请求的话则编写dopost函数,是get请求的话就编写doget函数。但此时web服务器还不能调用该servlet类,还需在web.xml文件中配置。

1、什么是容器?容器就是程序运行时需要的环境。Tomcat是servlet的运行环境,所以Tomcat是servlet容器。

那么Tomcat是怎样启动的呢?说到这里需要先介绍一下Servlet容器和Web容器的区别。Sevrlet容器是用来管理servlet的生命周期,而web容器,即web服务器是用来管理和部署Web应用的。Tomcat就是一个开源的Servlet容器,也是一个web容器---用于处理静态html,css等。Web服务器的作用说穿了就是:将某个主机上的资源映射为一个URL供外界访问。

什么是Servlet容器?

Servlet容器,顾名思义里面存放着Servlet对象。我们为什么能通过Web服务器映射的URL访问资源?肯定需要写程序处理请求,主要3个过程:

接收请求

处理请求

响应请求

任何一个应用程序,必然包括这三个步骤。其中接收请求和响应请求是共性功能,且没有差异性。任何一个应用程序,必然包括这三个步骤。其中接收请求和响应请求是共性功能,且没有差异性。于是,大家就把接收和响应两个步骤抽取成Web服务器,但处理请求的逻辑是不同的。没关系,抽取出来做成Servlet,交给程序员自己编写。

1.Servlet接口定义了三个生命周期方法:

public void init(ServletConfig config) throws ServletException

由 servlet 容器调用,指示将该 servlet 放入服务。

servlet 容器仅在实例化 servlet 之后调用 init 方法一次。在 servlet 可以接收任何请求之前,init 方法必须成功完成。

servlet 容器无法将 servlet 放入服务,如果 init 方法:

抛出 ServletException

未在 Web 服务器定义的时间段内返回

config包含 servlet 的配置和初始化参数的 ServletConfig 对象

ThrowsServletException: 如果发生妨碍 servlet 正常操作的异常

See alsojavax.servlet.UnavailableException, getServletConfig

public void service(ServletRequest req, ServletResponse res) throws ServletException, java.io.IOException

由 servlet 容器调用,以允许 servlet 响应某个请求。

此方法仅在 servlet 的 init() 方法成功完成之后调用。

应该为抛出或发送错误的 servlet 设置响应的状态代码。

servlet 通常运行在可同时处理多个请求的多线程 servlet 容器中。开发人员必须知道要同步对所有共享资源(比如文件、网络连接以及 servlet 的类和实例变量)的访问。有关 Java 中多线程编程的更多信息,可从 the Java tutorial on multi-threaded programming 中获得。

req包含客户端请求的 ServletRequest 对象

res包含 servlet 的响应的 ServletResponse 对象

ThrowsServletException: 如果发生妨碍 servlet 正常操作的异常

Throwsjava.io.IOException: 如果发生输入或输出异常

public void destroy()

由 servlet 容器调用,指示将从服务中取出该 servlet。此方法仅在 servlet 的 service 方法已退出或者在过了超时期之后调用一次。在调用此方法之后,servlet 容器不会再对此 servlet 调用 service 方法。

此方法为 servlet 提供了一个清除持有的所有资源(比如内存、文件句柄和线程)的机会,并确保任何持久状态都与内存中该 servlet 的当前状态保持同步。

除了生命周期方法之外,此接口还提供了ServletConfiggetServletConfig 方法和 StringgetServletInfo 方法,servlet 可使用前一种方法获得任何启动信息,而后一种方法允许 servlet 返回有关其自身的基本信息,比如作者、版本和版权。

public ServletConfig getServletConfig()

返回 ServletConfig 对象,该对象包含此 servlet 的初始化和启动参数。返回的 ServletConfig 对象是传递给 init 方法的对象。

此接口的实现负责存储 ServletConfig 对象,以便此方法可以返回该对象。实现此接口的 GenericServlet 类已经这样做了。

return初始化此 servlet 的 ServletConfig 对象

See alsoinit

public String getServletInfo()

返回有关 servlet 的信息,比如作者、版本和版权。

此方法返回的字符串应该是纯文本,不应该是任何种类的标记(比如 HTML、XML,等等)。

return包含 servlet 信息的 String

五个方法,最难的地方在于形参,然而Tomcat会事先把形参对象封装好传给我...除此以外,既不需要我写TCP连接数据库,也不需要我解析HTTP请求,更不需要我把结果转成HTTP响应,request对象和response对象帮我搞定了。

Tomcat之所以放心地交给我们实现,是因为Servlet里主要写的代码都是业务逻辑代码。和原始的、底层的解析、连接等没有丝毫关系。最难的几个操作,人家已经给你封装成形参传进来了。并且传入三个对象:ServletConfig、ServletRequest、ServletResponse。

ServletConfig---“Servlet配置”

也就是说,servletConfig对象封装了servlet的一些参数信息。如果需要,我们可以从它获取。

Request/Response

网络请求相关的内容,其实是Tomcat处理的并封装好了的,不需要Servlet操心。HTTP请求到了Tomcat后,Tomcat通过字符串解析,把各个请求头(Header),请求地址(URL),请求参数(QueryString)都封装进了Request对象中。通过调用

request.getHeader();

request.getUrl();

request.getQueryString();

...等等方法,都可以得到浏览器当初发送的请求信息。

至于Response,Tomcat传给Servlet时,它还是空的对象。Servlet逻辑处理后得到结果,最终通过response.write()方法,将结果写入response内部的缓冲区。Tomcat会在servlet处理结束后,拿到response,遍历里面的信息,组装成HTTP响应发给客户端。

Servlet接口5个方法,其中init、service、destroy是生命周期方法。init和destroy各自只执行一次,即servlet创建和销毁时。而service会在每次有新请求到来时被调用。也就是说,我们主要的业务代码需要写在service中。

GenericServlet,是个抽象类。实现了Servlet、ServletConfig等接口

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable

①重写了init()。GenericServlet类设置了一个成员变量servletConfig用于接收Tomcat传进来ServletConfig对象。init方法中还调用了一个init空参方法,如果我们希望在servlet创建时做一些什么初始化操作,可以继承GenericServlet后,覆盖init空参方法。

@Override

public void init(ServletConfig config) throws ServletException {

    this.config = config;

    this.init();

}

②重写了getServletConfig()。由于init方法是Servlet创建后最先调用,所以在这个方法调用时,成员变量config已经被赋值,可以得到config。

@Override

public ServletConfig getServletConfig() {

    return config;

}

③重写了getServletContext()。通过ServletConfig得到ServletContext。由于其他方法内也可以使用servletConfig,于是写了一个getServletContext方法。

@Override

public ServletContext getServletContext() {

    return getServletConfig().getServletContext();

}

④定义了一个抽象方法service,没有实现service()。

@Override

public abstract void service(ServletRequest req, ServletResponse res)

        throws ServletException, IOException;

⑤destory方法并不是销毁servlet的方法,而是真正销毁servlet前一定会调用的方法,主要还是为了关闭一些资源。

HttpServlet类继承了GenericServlet类,也是一个抽象类,并实现了service方法。

public abstract class HttpServlet extends GenericServlet

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) {

            // servlet doesn't support if-modified-since, no reason

            // to go through further expensive logic

            doGet(req, resp);

        } else {

            long ifModifiedSince;

            try {

                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);

            } catch (IllegalArgumentException iae) {

                // Invalid date header - proceed as if none was set

                ifModifiedSince = -1;

            }

            if (ifModifiedSince < (lastModified / 1000 * 1000)) {

                // If the servlet mod time is later, call doGet()

                // Round down to the nearest second for a proper compare

                // A ifModifiedSince of -1 will always be less

                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 {

        //

        // Note that this means NO servlet supports whatever

        // method was requested, anywhere on this server.

        //

        String errMsg = lStrings.getString("http.method_not_implemented");

        Object[] errArgs = new Object[1];

        errArgs[0] = method;

        errMsg = MessageFormat.format(errMsg, errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);

    }

}

也就是说HttpServlet的service方法已经替我们完成了复杂的请求方法判断。但是,翻遍整个HttpServlet源码,都没有找出一个抽象方法。所以为什么HttpServlet还要声明成抽象类呢?

一个类声明成抽象方法,一般有两个原因:

有抽象方法

没有抽象方法,但是不希望被实例化

HttpServlet做成抽象类,仅仅是为了不让new。

HttpServlet不希望被实例化,且要求子类重写doGet、doPost等方法。如果我们没重写会怎样?浏览器页面会显示:405(http.method_get_not_supported)

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

    throws ServletException, IOException

{

    String protocol = req.getProtocol();

    String msg = lStrings.getString("http.method_get_not_supported");

    if (protocol.endsWith("1.1")) {

        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);

    } else {

        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);

    }

}

protected void doPost(HttpServletRequest req, HttpServletResponse resp)

    throws ServletException, IOException {

    String protocol = req.getProtocol();

    String msg = lStrings.getString("http.method_post_not_supported");

    if (protocol.endsWith("1.1")) {

        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);

    } else {

        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);

    }

}

也就是说,HttpServlet虽然在service中帮我们写了请求方式的判断。但是针对每一种请求,业务逻辑代码是不同的,HttpServlet无法知晓子类想干嘛,所以就抽出七个方法,并且提供了默认实现:报405、400错误,提示请求不支持。但这种实现本身非常鸡肋,简单来说就是等于没有。所以,不能让它被实例化,不然调用doXxx方法是无用功。

所以HttpServlet的子类必须至少重写以下方法中的一个:

doGet

doPost

doPut

doDelete

init

getServletInfo

2.javax.servlet

Interface ServletConfig

All Known Implementing Classes:

GenericServlet, HttpServlet

public interfaceServletConfig

Implemented by: GenericServlet

servlet 容器使用的 servlet 配置对象,该对象在初始化期间将信息传递给 servlet。

Method Detail

public String getServletName()

返回此 servlet 实例的名称。该名称可能是通过服务器管理提供的,在 Web 应用程序部署描述符中分配,或者对于未注册(和未命名)的 servlet 实例,该名称将是该 servlet 的类名称。

returnservlet 实例的名称

public ServletContextgetServletContext()

返回对调用者在其中执行操作的 ServletContext 的引用。

return一个 ServletContext 对象,调用者用于与其 servlet 容器交互

public String getInitParameter(String name)

返回包含指定初始化参数的值的 String,如果参数不存在,则返回 null。

name指定初始化参数名称的 String

return包含初始化参数值的 String

public java.util.Enumeration getInitParameterNames()

以 String 对象的 Enumeration 的形式返回 servlet 的初始化参数的名称,如果 servlet 没有初始化参数,则返回一个空的 Enumeration。

return包含 servlet 初始化参数名称的 String 对象的 Enumeration

3.javax.servlet

Interface ServletContext

public interfaceServletContext

定义一组方法,servlet 使用这些方法与其 servlet 容器进行通信,例如,获取文件的 MIME 类型、分发请求或写入日志文件。每个 Java 虚拟机的每个“Web 应用程序”都有一个上下文。(“Web 应用程序”是 servlet 和内容的 Collection,这些 servlet 和内容安装在服务器的 URL 名称空间(比如 /catalog)的特定子集下,并且可能通过 .war 文件安装。)如果 Web 应用程序在其部署描述符中标记为 "distributed",那么每个虚拟机都将有一个上下文实例。在这种情况下,不能将上下文用作共享全局信息的位置(因为该信息不会是真正全局共享的)。请使用外部资源(如数据库)替代。ServletContext 对象包含在 ServletConfig 对象中,ServletConfig 对象在初始化 servlet 时由 Web 服务器提供给 servlet。

Method Detail

public String getContextPath()

返回 Web 应用程序的上下文路径。

上下文路径是用来选择请求上下文的请求 URI 的一部分。请求 URI 中首先出现的总是上下文路径。路径以 "/" 字符开头但不以 "/" 字符结束。对于默认(根)上下文中的 servlet,此方法返回 ""。

servlet 容器可能通过多个上下文路径匹配一个上下文。在这种情况下,getContextPath() 将返回该请求使用的实际上下文路径,它可能不同于此方法返回的路径。此方法返回的上下文路径应该被认为是应用程序的主要或首选上下文路径。

returnWeb 应用程序的上下文路径;对于默认(根)上下文,返回 ""

public ServletContext getContext(String uripath)

返回与服务器上的指定 URL 相对应的 ServletContext 对象。

此方法允许 servlet 获得对服务器各个部分的上下文的访问,并根据需要从上下文中获得 RequestDispatcher 对象。给定路径必须以 "/" 开头,相对于服务器的文档根进行解释,并与宿主在此容器上的其他 Web 应用程序的上下文根匹配。

在安全意识较强的环境中,servlet 容器可能对给定 URL 返回 null。

uripath指定容器中另一个 Web 应用程序的上下文路径的 String。

return与指定 URL 相对应的 ServletContext 对象;如果该对象不存在,或者容器希望限制此访问,则返回 null。

你可能感兴趣的:(Servlet的本质,它是如何工作的)